Merge pull request #1380 from omnivore-app/fix/ios-note-saving

Improve note saving reliability on iOS
This commit is contained in:
Jackson Harper 2022-11-01 23:19:28 +08:00 committed by GitHub
commit 1ad5fdb838
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 437 additions and 163 deletions

View file

@ -83,8 +83,11 @@ final class AppStoreScreenshots: XCTestCase {
func testScreenshotSubscriptions() throws {
let app = XCUIApplication()
setupSnapshot(app)
app.navigationBars["Home"]/*@START_MENU_TOKEN@*/ .buttons["_profile"]/*[[".otherElements[\"_profile\"].buttons[\"_profile\"]",".buttons[\"_profile\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/ .tap()
app.collectionViews.buttons["Subscriptions"].tap()
// app.navigationBars.firstMatch.buttons["person.circle"].tap()
// app.collectionViews.buttons["Subscriptions"].tap()
//
// XCUIApplication().navigationBars["_TtGC7SwiftUI19UIHosting"]/*@START_MENU_TOKEN@*/.buttons["ToggleSidebar"]/*[[".buttons[\"Show Sidebar\"]",".buttons[\"ToggleSidebar\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap()
//
snapshot("Newsletters")

View file

@ -25,12 +25,12 @@ import Views
func handleArchiveAction(dataService: DataService) {
guard let objectID = item?.objectID ?? pdfItem?.objectID else { return }
dataService.archiveLink(objectID: objectID, archived: !isItemArchived)
Snackbar.show(message: !isItemArchived ? "Link archived" : "Link moved to Inbox")
showInSnackbar(!isItemArchived ? "Link archived" : "Link moved to Inbox")
}
func handleDeleteAction(dataService: DataService) {
guard let objectID = item?.objectID ?? pdfItem?.objectID else { return }
Snackbar.show(message: "Link removed")
showInSnackbar("Link removed")
dataService.removeLink(objectID: objectID)
}

View file

@ -17,6 +17,7 @@ struct WebReader: PlatformViewRepresentable {
@Binding var shareActionID: UUID?
@Binding var annotation: String
@Binding var showBottomBar: Bool
@Binding var showHighlightAnnotationModal: Bool
func makeCoordinator() -> WebReaderCoordinator {
WebReaderCoordinator()
@ -84,11 +85,19 @@ struct WebReader: PlatformViewRepresentable {
private func updatePlatformView(_ webView: WKWebView, context: Context) {
if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID {
context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID
(webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation))
do {
try (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation))
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
showHighlightAnnotationModal = false
}
} catch {
showInSnackbar("Error saving note.")
}
}
if readerSettingsChangedTransactionID != context.coordinator.previousReaderSettingsChangedUUID {
context.coordinator.previousReaderSettingsChangedUUID = readerSettingsChangedTransactionID
(webView as? OmnivoreWebView)?.updateTheme()
(webView as? OmnivoreWebView)?.updateFontFamily()
(webView as? OmnivoreWebView)?.updateFontSize()
(webView as? OmnivoreWebView)?.updateTextContrast()

View file

@ -299,7 +299,8 @@ struct WebReaderContainerView: View {
showNavBarActionID: $showNavBarActionID,
shareActionID: $shareActionID,
annotation: $annotation,
showBottomBar: $showBottomBar
showBottomBar: $showBottomBar,
showHighlightAnnotationModal: $showHighlightAnnotationModal
)
.onTapGesture {
withAnimation {
@ -317,7 +318,6 @@ struct WebReaderContainerView: View {
annotation: $annotation,
onSave: {
annotationSaveTransactionID = UUID()
showHighlightAnnotationModal = false
},
onCancel: {
showHighlightAnnotationModal = false

View file

@ -120,7 +120,7 @@ struct SpeechSynthesizer {
func createPlayerItems(from: Int) -> [SpeechItem] {
var result: [SpeechItem] = []
for idx in from ..< document.utterances.count {
for idx in from ..< min(7, document.utterances.count) {
let utterance = document.utterances[idx]
let voiceStr = utterance.voice ?? document.defaultVoice
let segmentStr = String(format: "%04d", arguments: [idx])

View file

@ -0,0 +1,18 @@
//
// ShowInSnackbar.swift
//
//
// Created by Jackson Harper on 11/1/22.
//
import Foundation
public func showInSnackbar(_ message: String) {
let nname = Notification.Name("OperationSuccess")
NotificationCenter.default.post(name: nname, object: nil, userInfo: ["message": message])
}
public func showErrorInSnackbar(_ message: String) {
let nname = Notification.Name("OperationFailure")
NotificationCenter.default.post(name: nname, object: nil, userInfo: ["message": message])
}

View file

@ -19,4 +19,5 @@ public enum UserDefaultKey: String {
case textToSpeechPreloadEnabled
case recentSearchTerms
case audioPlayerExpanded
case themeName
}

View file

@ -1,3 +1,4 @@
import Models
import Utils
import WebKit
@ -31,12 +32,6 @@ public final class OmnivoreWebView: WKWebView {
self.isFindInteractionEnabled = true
}
#endif
NotificationCenter.default.addObserver(forName: NSNotification.Name("SpeakingReaderItem"), object: nil, queue: OperationQueue.main, using: { notification in
if let pageID = notification.userInfo?["pageID"] as? String, let anchorIdx = notification.userInfo?["anchorIdx"] as? String {
self.dispatchEvent(.speakingSection(anchorIdx: anchorIdx))
}
})
}
@available(*, unavailable)
@ -44,15 +39,33 @@ public final class OmnivoreWebView: WKWebView {
fatalError("init(coder:) has not been implemented")
}
public func updateTheme() {
do {
if let themeName = UserDefaults.standard.value(forKey: UserDefaultKey.themeName.rawValue) as? String {
try dispatchEvent(.updateTheme(themeName: "Gray" /* themeName */ ))
}
} catch {
showErrorInSnackbar("Error updating theme")
}
}
public func updateFontFamily() {
if let fontFamily = UserDefaults.standard.value(forKey: UserDefaultKey.preferredWebFont.rawValue) as? String {
dispatchEvent(.updateFontFamily(family: fontFamily))
do {
if let fontFamily = UserDefaults.standard.value(forKey: UserDefaultKey.preferredWebFont.rawValue) as? String {
try dispatchEvent(.updateFontFamily(family: fontFamily))
}
} catch {
showErrorInSnackbar("Error updating font")
}
}
public func updateFontSize() {
if let fontSize = UserDefaults.standard.value(forKey: UserDefaultKey.preferredWebFontSize.rawValue) as? Int {
dispatchEvent(.updateFontSize(size: fontSize))
do {
if let fontSize = UserDefaults.standard.value(forKey: UserDefaultKey.preferredWebFontSize.rawValue) as? Int {
try dispatchEvent(.updateFontSize(size: fontSize))
}
} catch {
showErrorInSnackbar("Error updating font")
}
}
@ -60,13 +73,21 @@ public final class OmnivoreWebView: WKWebView {
if let maxWidthPercentage = UserDefaults.standard.value(
forKey: UserDefaultKey.preferredWebMaxWidthPercentage.rawValue
) as? Int {
dispatchEvent(.updateMaxWidthPercentage(maxWidthPercentage: maxWidthPercentage))
do {
try dispatchEvent(.updateMaxWidthPercentage(maxWidthPercentage: maxWidthPercentage))
} catch {
showErrorInSnackbar("Error updating max width")
}
}
}
public func updateLineHeight() {
if let height = UserDefaults.standard.value(forKey: UserDefaultKey.preferredWebLineSpacing.rawValue) as? Int {
dispatchEvent(.updateLineHeight(height: height))
do {
try dispatchEvent(.updateLineHeight(height: height))
} catch {
showErrorInSnackbar("Error updating line height")
}
}
}
@ -76,17 +97,35 @@ public final class OmnivoreWebView: WKWebView {
) as? Bool
if let isHighContrast = isHighContrast {
dispatchEvent(.handleFontContrastChange(isHighContrast: isHighContrast))
do {
try dispatchEvent(.handleFontContrastChange(isHighContrast: isHighContrast))
} catch {
showErrorInSnackbar("Error updating text contrast")
}
}
}
public func shareOriginalItem() {
dispatchEvent(.share)
do {
try dispatchEvent(.share)
} catch {
showErrorInSnackbar("Error updating line height")
}
}
public func dispatchEvent(_ event: WebViewDispatchEvent) {
evaluateJavaScript(event.script) { _, err in
if let err = err { print("evaluateJavaScript error", err) }
public func dispatchEvent(_ event: WebViewDispatchEvent) throws {
let script = try event.script
var errResult: Error?
evaluateJavaScript(script) { _, err in
if let err = err {
print("evaluateJavaScript error", err)
errResult = err
}
}
if let errResult = errResult {
throw errResult
}
}
@ -94,7 +133,11 @@ public final class OmnivoreWebView: WKWebView {
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
guard previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle else { return }
dispatchEvent(.updateColorMode(isDark: traitCollection.userInterfaceStyle == .dark))
do {
try dispatchEvent(.updateColorMode(isDark: traitCollection.userInterfaceStyle == .dark))
} catch {
showErrorInSnackbar("Error updating theme")
}
}
#elseif os(macOS)
@ -204,28 +247,48 @@ public final class OmnivoreWebView: WKWebView {
}
@objc private func annotateSelection() {
dispatchEvent(.annotate)
do {
try dispatchEvent(.annotate)
} catch {
showErrorInSnackbar("Error creating highlight")
}
hideMenu()
}
@objc private func highlightSelection() {
dispatchEvent(.highlight)
do {
try dispatchEvent(.highlight)
} catch {
showErrorInSnackbar("Error creating highlight")
}
hideMenu()
}
@objc private func shareSelection() {
dispatchEvent(.share)
do {
try dispatchEvent(.share)
} catch {
showErrorInSnackbar("Error sharing highlight")
}
hideMenu()
}
@objc private func removeSelection() {
dispatchEvent(.remove)
do {
try dispatchEvent(.remove)
} catch {
showErrorInSnackbar("Error deleting highlight")
}
hideMenu()
}
@objc override public func copy(_ sender: Any?) {
super.copy(sender)
dispatchEvent(.copyHighlight)
do {
try dispatchEvent(.copyHighlight)
} catch {
showErrorInSnackbar("Error copying highlight")
}
hideMenu()
}
@ -259,7 +322,7 @@ public final class OmnivoreWebView: WKWebView {
private func hideMenuAndDismissHighlight() {
hideMenu()
dispatchEvent(.dismissHighlight)
try? dispatchEvent(.dismissHighlight)
}
private func showHighlightMenu(_ rect: CGRect) {
@ -293,6 +356,7 @@ public enum WebViewDispatchEvent {
case updateFontSize(size: Int)
case updateColorMode(isDark: Bool)
case updateFontFamily(family: String)
case updateTheme(themeName: String)
case saveAnnotation(annotation: String)
case annotate
case highlight
@ -303,7 +367,10 @@ public enum WebViewDispatchEvent {
case speakingSection(anchorIdx: String)
var script: String {
"var event = new Event('\(eventName)');\(scriptPropertyLine)document.dispatchEvent(event);"
get throws {
let propertyLine = try scriptPropertyLine
return "var event = new Event('\(eventName)');\(propertyLine)document.dispatchEvent(event);"
}
}
private var eventName: String {
@ -320,6 +387,8 @@ public enum WebViewDispatchEvent {
return "updateColorMode"
case .updateFontFamily:
return "updateFontFamily"
case .updateTheme:
return "updateTheme"
case .saveAnnotation:
return "saveAnnotation"
case .annotate:
@ -340,25 +409,35 @@ public enum WebViewDispatchEvent {
}
private var scriptPropertyLine: String {
switch self {
case let .handleFontContrastChange(isHighContrast: isHighContrast):
return "event.fontContrast = '\(isHighContrast ? "high" : "normal")';"
case let .updateLineHeight(height: height):
return "event.lineHeight = '\(height)';"
case let .updateMaxWidthPercentage(maxWidthPercentage: maxWidthPercentage):
return "event.maxWidthPercentage = '\(maxWidthPercentage)';"
case let .updateFontSize(size: size):
return "event.fontSize = '\(size)';"
case let .updateColorMode(isDark: isDark):
return "event.isDark = '\(isDark)';"
case let .updateFontFamily(family: family):
return "event.fontFamily = '\(family)';"
case let .saveAnnotation(annotation: annotation):
return "event.annotation = '\(annotation)';"
case let .speakingSection(anchorIdx: anchorIdx):
return "event.anchorIdx = '\(anchorIdx)';"
case .annotate, .highlight, .share, .remove, .copyHighlight, .dismissHighlight:
return ""
get throws {
switch self {
case let .handleFontContrastChange(isHighContrast: isHighContrast):
return "event.fontContrast = '\(isHighContrast ? "high" : "normal")';"
case let .updateLineHeight(height: height):
return "event.lineHeight = '\(height)';"
case let .updateMaxWidthPercentage(maxWidthPercentage: maxWidthPercentage):
return "event.maxWidthPercentage = '\(maxWidthPercentage)';"
case let .updateTheme(themeName: themeName):
return "event.themeName = '\(themeName)';"
case let .updateFontSize(size: size):
return "event.fontSize = '\(size)';"
case let .updateColorMode(isDark: isDark):
return "event.isDark = '\(isDark)';"
case let .updateFontFamily(family: family):
return "event.fontFamily = '\(family)';"
case let .saveAnnotation(annotation: annotation):
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(annotation) {
let str = String(decoding: encoded, as: UTF8.self)
return "event.annotation = '\(str)';"
} else {
throw BasicError.message(messageText: "Unable to serialize highlight note.")
}
case let .speakingSection(anchorIdx: anchorIdx):
return "event.anchorIdx = '\(anchorIdx)';"
case .annotate, .highlight, .share, .remove, .copyHighlight, .dismissHighlight:
return ""
}
}
}
}

View file

@ -69,81 +69,112 @@ public enum WebFont: String, CaseIterable {
.navigationTitle("Reader Font")
}
var themePicker: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
ForEach(Theme.allCases, id: \.self) { theme in
VStack {
ZStack {
Circle()
.foregroundColor(theme.bgColor)
.frame(minWidth: 32, minHeight: 32)
.padding(8)
}
Text(theme.rawValue).font(.appCaption)
}
.padding(8)
.background(Color(red: 248 / 255.0, green: 248 / 255.0, blue: 248 / 255.0))
.onTapGesture {
ThemeManager.currentThemeName = theme.rawValue
updateReaderPreferences()
}
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(ThemeManager.currentThemeName == theme.rawValue ? Color.appCtaYellow : .clear, lineWidth: 2)
)
.padding(2)
}
}
}
}
public var body: some View {
NavigationView {
ScrollView(showsIndicators: false) {
VStack(alignment: .center) {
VStack {
LabelledStepper(
labelText: "Font Size:",
onIncrement: {
storedFontSize = min(storedFontSize + 2, 28)
updateReaderPreferences()
},
onDecrement: {
storedFontSize = max(storedFontSize - 2, 10)
updateReaderPreferences()
}
)
VStack(alignment: .center) {
// themePicker
// .padding(.bottom, 16)
LabelledStepper(
labelText: "Margin:",
onIncrement: {
storedMaxWidthPercentage = max(storedMaxWidthPercentage - 10, 40)
updateReaderPreferences()
},
onDecrement: {
storedMaxWidthPercentage = min(storedMaxWidthPercentage + 10, 100)
updateReaderPreferences()
}
)
LabelledStepper(
labelText: "Line Spacing:",
onIncrement: {
storedLineSpacing = min(storedLineSpacing + 25, 300)
updateReaderPreferences()
},
onDecrement: {
storedLineSpacing = max(storedLineSpacing - 25, 100)
updateReaderPreferences()
}
)
Toggle("High Contrast Text:", isOn: $prefersHighContrastText)
.frame(height: 40)
.padding(.trailing, 6)
.onChange(of: prefersHighContrastText) { _ in
updateReaderPreferences()
}
HStack {
NavigationLink(destination: fontList) {
Text("Change Reader Font")
}
Image(systemName: "chevron.right")
Spacer()
}
.frame(height: 40)
Spacer()
LabelledStepper(
labelText: "Font Size",
onIncrement: {
storedFontSize = min(storedFontSize + 2, 28)
updateReaderPreferences()
},
onDecrement: {
storedFontSize = max(storedFontSize - 2, 10)
updateReaderPreferences()
}
)
LabelledStepper(
labelText: "Margin",
onIncrement: {
storedMaxWidthPercentage = max(storedMaxWidthPercentage - 10, 40)
updateReaderPreferences()
},
onDecrement: {
storedMaxWidthPercentage = min(storedMaxWidthPercentage + 10, 100)
updateReaderPreferences()
}
)
LabelledStepper(
labelText: "Line Spacing",
onIncrement: {
storedLineSpacing = min(storedLineSpacing + 25, 300)
updateReaderPreferences()
},
onDecrement: {
storedLineSpacing = max(storedLineSpacing - 25, 100)
updateReaderPreferences()
}
)
HStack {
NavigationLink(destination: fontList) {
Text("Font")
}
Spacer()
Button(action: {}, label: { Text("Crimson Text").frame(width: 91) })
.buttonStyle(RoundedRectButtonStyle())
}
.frame(height: 40)
Toggle("High Contrast Text:", isOn: $prefersHighContrastText)
.frame(height: 40)
.padding(.trailing, 6)
.onChange(of: prefersHighContrastText) { _ in
updateReaderPreferences()
}
Spacer()
}
.padding()
.navigationTitle("Reader Preferences")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .barTrailing) {
Button(
action: dismissAction,
label: { Text("Done").foregroundColor(.appGrayTextContrast).padding() }
)
}
}
// .toolbar {
// ToolbarItem(placement: .barTrailing) {
// Button(
// action: dismissAction,
// label: { Text("Done").foregroundColor(.appGrayTextContrast).padding() }
// )
// }
// }
}
.navigationViewStyle(.stack)
.accentColor(.appGrayTextContrast)
// .navigationViewStyle(.stack)
// .accentColor(.appGrayTextContrast)
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,54 @@
//
// File.swift
//
//
// Created by Jackson Harper on 10/27/22.
//
import Foundation
import SwiftUI
import Utils
public enum Theme: String, CaseIterable {
case system = "System"
case sepia = "Sepia"
case charcoal = "Charcoal"
case mint = "Mint"
case solarized = "Solarized"
case light = "Light"
case dark = "Dark"
public var bgColor: Color {
switch self {
case .system:
return Color.systemBackground
case .charcoal:
return Color(red: 48 / 255.0, green: 48 / 255.0, blue: 48 / 255.0)
case .sepia:
return Color(red: 249 / 255.0, green: 241 / 255.0, blue: 220 / 255.0)
case .mint:
return Color(red: 202 / 255.0, green: 230 / 255.0, blue: 208 / 255.0)
case .solarized:
return Color(red: 13 / 255.0, green: 39 / 255.0, blue: 50 / 255.0)
case .light:
return Color.white
case .dark:
return Color.black
}
}
public static func fromName(themeName: String) -> Theme? {
for theme in Theme.allCases {
if theme.rawValue == themeName {
return theme
}
}
return nil
}
}
public enum ThemeManager {
@AppStorage(UserDefaultKey.themeName.rawValue) public static var currentThemeName = "System"
}

View file

@ -19,6 +19,7 @@ import WebKit
webView.isOpaque = false
webView.backgroundColor = UIColor.clear
if let url = request.url {
// let themeID = Color.isDarkMode ? "Gray" /* "Sepia" */ : "Charcoal"
let themeID = Color.isDarkMode ? "Gray" : "LightGray"
webView.injectCookie(cookieString: "theme=\(themeID); Max-Age=31536000;", url: url)
}
@ -51,7 +52,7 @@ import WebKit
if let url = request.url {
// Dark mode is still rendering a white background on mac for some reason.
// Forcing light mode for now until we figure out a fix
let themeID = "LightGray" // NSApp.effectiveAppearance.name == NSAppearance.Name.darkAqua ? "Gray" : "LightGray"
let themeID = "Charcoal" // NSApp.effectiveAppearance.name == NSAppearance.Name.darkAqua ? "Gray" : "LightGray"
webView.injectCookie(cookieString: "theme=\(themeID); Max-Age=31536000;", url: url)
}
return webView

View file

@ -11,7 +11,7 @@ import { ReportIssuesModal } from './ReportIssuesModal'
import { reportIssueMutation } from '../../../lib/networking/mutations/reportIssueMutation'
import { ArticleHeaderToolbar } from './ArticleHeaderToolbar'
import { userPersonalizationMutation } from '../../../lib/networking/mutations/userPersonalizationMutation'
import { updateThemeLocally } from '../../../lib/themeUpdater'
import { updateTheme, updateThemeLocally } from '../../../lib/themeUpdater'
import { ArticleMutations } from '../../../lib/articleActions'
import { LabelChip } from '../../elements/LabelChip'
import { Label } from '../../../lib/networking/fragments/labelFragment'
@ -121,6 +121,17 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
}
}
interface UpdateThemeEvent extends Event {
themeName?: string
}
const handleThemeChange = async (event: UpdateThemeEvent) => {
const newTheme = event.themeName
if (newTheme) {
updateTheme(newTheme)
}
}
interface UpdateColorModeEvent extends Event {
isDark?: string
}
@ -145,6 +156,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
'updateMaxWidthPercentage',
updateMaxWidthPercentage
)
document.addEventListener('updateTheme', handleThemeChange)
document.addEventListener('updateFontSize', handleFontSizeChange)
document.addEventListener('updateColorMode', updateColorMode)
document.addEventListener(
@ -160,6 +172,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
'updateMaxWidthPercentage',
updateMaxWidthPercentage
)
document.removeEventListener('updateTheme', handleThemeChange)
document.removeEventListener('updateFontSize', handleFontSizeChange)
document.removeEventListener('updateColorMode', updateColorMode)
document.removeEventListener(
@ -179,7 +192,6 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
readerFontColor: highContrastFont
? theme.colors.readerFontHighContrast.toString()
: theme.colors.readerFont.toString(),
readerFontColorTransparent: theme.colors.readerFontTransparent.toString(),
readerTableHeaderColor: theme.colors.readerTableHeader.toString(),
readerHeadersColor: theme.colors.readerHeader.toString(),
}
@ -193,7 +205,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
maxWidth: `${styles.maxWidthPercentage ?? 100}%`,
background: props.isAppleAppEmbed
? 'unset'
: theme.colors.grayBg.toString(),
: theme.colors.readerBg.toString(),
'--text-font-family': styles.fontFamily,
'--text-font-size': `${styles.fontSize}px`,
'--line-height': `${styles.lineHeight}%`,
@ -202,7 +214,6 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
'--figure-margin': '1.6rem auto',
'--hr-margin': '1em',
'--font-color': styles.readerFontColor,
'--font-color-transparent': styles.readerFontColorTransparent,
'--table-header-color': styles.readerTableHeaderColor,
'--headers-color': styles.readerHeadersColor,
'@sm': {

View file

@ -126,7 +126,7 @@ export function ReaderSettingsControl(props: ReaderSettingsProps): JSX.Element {
}}
>
<StyledText
color={theme.colors.readerFontTransparent.toString()}
color={theme.colors.readerFont.toString()}
css={{ pl: '12px', m: '0px', pt: '14px' }}
>
Margin:
@ -193,7 +193,7 @@ export function ReaderSettingsControl(props: ReaderSettingsProps): JSX.Element {
}}
>
<StyledText
color={theme.colors.readerFontTransparent.toString()}
color={theme.colors.readerFont.toString()}
css={{ pl: '12px', m: '0px', pt: '14px' }}
>
Line Spacing:

View file

@ -17,7 +17,6 @@ export function SkeletonArticleContainer(props: SkeletonArticleContainerProps):
lineHeight: props.lineHeight ?? 150,
fontFamily: props.fontFamily ?? 'inter',
readerFontColor: theme.colors.readerFont.toString(),
readerFontColorTransparent: theme.colors.readerFontTransparent.toString(),
readerTableHeaderColor: theme.colors.readerTableHeader.toString(),
readerHeadersColor: theme.colors.readerHeader.toString(),
}
@ -40,7 +39,6 @@ export function SkeletonArticleContainer(props: SkeletonArticleContainerProps):
'--figure-margin': '1.6rem auto',
'--hr-margin': '1em',
'--font-color': styles.readerFontColor,
'--font-color-transparent': styles.readerFontColorTransparent,
'--table-header-color': styles.readerTableHeaderColor,
'--headers-color': styles.readerHeadersColor,
'@sm': {

View file

@ -6,6 +6,8 @@ export enum ThemeId {
Light = 'LightGray',
Dark = 'Gray',
Darker = 'Dark',
Sepia = 'Sepia',
Charcoal = 'Charcoal'
}
export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
@ -211,7 +213,6 @@ const darkThemeSpec = {
readerBg: '#303030',
readerFont: '#b9b9b9',
readerFontHighContrast: 'white',
readerFontTransparent: 'rgba(185,185,185,0.65)',
readerHeader: '#b9b9b9',
readerTableHeader: '#FFFFFF',
tooltipIcons: '#5F5E58',
@ -236,12 +237,35 @@ const darkThemeSpec = {
},
}
// Avatar Fallback color
const sepiaThemeSpec = {
colors: {
// Reader Colors
readerBg: '#F9F1DC',
readerFont: '#554A34',
readerFontHighContrast: 'black',
readerHeader: '554A34',
readerTableHeader: '#FFFFFF',
}
}
const charcoalThemeSpec = {
colors: {
// Reader Colors
readerBg: '#303030',
readerFont: '#b9b9b9',
readerFontHighContrast: 'white',
readerHeader: '#b9b9b9',
readerTableHeader: '#FFFFFF',
}
}
// Dark and Darker theme now match each other.
// Use the darkThemeSpec object to make updates.
export const darkTheme = createTheme(ThemeId.Dark, darkThemeSpec)
export const darkerTheme = createTheme(ThemeId.Darker, darkThemeSpec)
export const sepiaTheme = createTheme(ThemeId.Sepia, {...darkThemeSpec, ...sepiaThemeSpec})
export const charcoalTheme = createTheme(ThemeId.Charcoal, {...darkThemeSpec, ...charcoalThemeSpec})
// Lighter theme now matches the default theme.
// This only exists for users that might still have a lighter theme set

View file

@ -3,6 +3,8 @@ import {
lighterTheme,
darkTheme,
darkerTheme,
sepiaTheme,
charcoalTheme,
} from '../components/tokens/stitches.config'
import { userPersonalizationMutation } from './networking/mutations/userPersonalizationMutation'
@ -24,9 +26,14 @@ export function updateThemeLocally(themeId: string): void {
document.body.classList.remove(
lighterTheme,
ThemeId.Light,
darkTheme,
darkerTheme
darkerTheme,
ThemeId.Light,
ThemeId.Dark,
ThemeId.Darker,
ThemeId.Lighter,
ThemeId.Sepia,
ThemeId.Charcoal,
)
document.body.classList.add(themeId)
}
@ -41,6 +48,10 @@ export function currentThemeName(): string {
return 'Darker'
case ThemeId.Lighter:
return 'Lighter'
case ThemeId.Sepia:
return 'Sepia'
case ThemeId.Charcoal:
return 'Charcoal'
default:
return ''
}

View file

@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable functional/no-class */
import { useEffect } from 'react'
import NextDocument, { Html, Head, Main, NextScript } from 'next/document'
import { getCssText, globalStyles } from '../components/tokens/stitches.config'
@ -35,7 +34,7 @@ export default class Document extends NextDocument {
var themeId = window.localStorage.getItem('theme')
if (themeId) {
document.body.classList.remove('theme-default', 'White', 'Gray', 'LightGray', 'Dark')
document.body.classList.remove('theme-default', 'White', 'Gray', 'LightGray', 'Dark', 'Sepia', 'Charcoal')
document.body.classList.add(themeId)
}
`

View file

@ -362,8 +362,9 @@ on smaller screens we display the note icon
width: 20%;
}
._omnivore-static-tweet {
background: #ffffff;
.article-inner-css ._omnivore-static-tweet {
color: #0F1419 !important;
background: #ffffff !important;
display: flex;
flex-direction: column;
gap: 12px;
@ -377,74 +378,108 @@ on smaller screens we display the note icon
-webkit-font-smoothing: subpixel-antialiased;
}
._omnivore-static-tweet-header {
display: flex;
align-items: center;
flex-direction: row;
gap: 12px;
._omnivore-static-tweet ._omnivore-static-tweet-header {
display: flex;
align-items: center;
flex-direction: row;
gap: 12px;
margin: unset;
justify-content: flex-start;
color: #0F1419 !important;
}
._omnivore-static-tweet-link-top {
._omnivore-static-tweet-header ._omnivore-static-tweet-header-text {
display: flex;
flex-direction: column;
color: #0F1419 !important;
color: #496F72;
font-size: 14px;
line-height: 20px;
}
._omnivore-static-tweet-header-text .tweet-author-name {
font-weight: 600;
}
._omnivore-static-tweet-header-text .tweet-author-handle {
color: #808080;
}
._omnivore-static-tweet-header .tweet-fake-link {
color: #1da1f2;
}
._omnivore-static-tweet ._omnivore-static-tweet-text {
font-size: 14px;
line-height: 20px;
color: #0F1419 !important;
}
._omnivore-static-tweet ._omnivore-static-tweet-link-top {
display: flex;
flex-direction: column;
gap: 12px;
text-decoration: none;
}
._omnivore-static-tweet-header-avatar {
._omnivore-static-tweet ._omnivore-static-tweet-header-avatar {
-ms-interpolation-mode: bicubic;
border: none !important;
border-radius: 50%;
float: left;
height: 48px;
width: 48px;
margin: 0;
margin: unset !important;
margin-right: 12px;
max-width: 100%;
vertical-align: middle;
}
._omnivore-static-tweet-header {
display: flex;
align-items: center;
flex-direction: row;
gap: 12px;
._omnivore-static-tweet ._omnivore-static-tweet-link-bottom {
display: flex;
flex-direction: column;
text-decoration: none;
white-space: pre-wrap;
}
._omnivore-static-tweet-link-bottom {
display: flex;
flex-direction: column;
text-decoration: none;
white-space: pre-wrap;
._omnivore-static-tweet ._omnivore-static-tweet-footer {
display: flex;
flex-direction: column;
align-items: flex-start;
color: #808080;
font-size: 14px;
line-height: 20px;
}
._omnivore-static-tweet-footer {
display: flex;
flex-direction: column;
align-items: flex-start;
}
._omnivore-static-tweet-footer hr {
._omnivore-static-tweet ._omnivore-static-tweet-footer hr {
background: #e0e0e0;
border: none;
height: 1px;
margin: 12px 0;
padding: 0;
width: 100%;
color: #496F72;
font-size: 14px;
line-height: 20px;
}
._omnivore-static-tweet-author-handle {
._omnivore-static-tweet ._omnivore-static-tweet-author-handle {
display: block;
}
._omnivore-static-tweet-ufi {
._omnivore-static-tweet ._omnivore-static-tweet-ufi {
display: flex;
gap: 24px;
align-items: center;
}
._omnivore-static-tweet-ufi .likes, .retweets {
._omnivore-static-tweet ._omnivore-static-tweet-ufi .likes, .retweets {
display: flex;
gap: 4px;
text-decoration: none;
color: #808080;
font-size: 14px;
line-height: 20px;
}