Shared highlights UX

This commit is contained in:
Jackson Harper
2022-12-12 23:49:15 +08:00
parent 847a848d0e
commit e8cd9f4412
32 changed files with 312 additions and 161 deletions

View File

@ -2,31 +2,31 @@ import SwiftUI
import Views
// TODO: maybe move this into Views package?
struct IconButtonView: View {
let title: String
let systemIconName: String
let action: () -> Void
var body: some View {
Button(action: action) {
VStack(alignment: .center, spacing: 8) {
Image(systemName: systemIconName)
.font(.appTitle)
.foregroundColor(.appYellow48)
Text(title)
.font(.appBody)
.foregroundColor(.appGrayText)
}
.frame(
maxWidth: .infinity,
maxHeight: .infinity
)
.background(Color.appButtonBackground)
.cornerRadius(8)
}
.frame(height: 100)
}
}
// struct IconButtonView: View {
// let title: String
// let systemIconName: String
// let action: () -> Void
//
// var body: some View {
// Button(action: action) {
// VStack(alignment: .center, spacing: 8) {
// Image(systemName: systemIconName)
// .font(.appTitle)
// .foregroundColor(.appYellow48)
// Text(title)
// .font(.appBody)
// .foregroundColor(.appGrayText)
// }
// .frame(
// maxWidth: .infinity,
// maxHeight: .infinity
// )
// .background(Color.appButtonBackground)
// .cornerRadius(8)
// }
// .frame(height: 100)
// }
// }
struct CheckmarkButtonView: View {
let titleText: String

View File

@ -107,10 +107,37 @@ struct HighlightsListCard: View {
}
.padding(.top, 16)
if let createdBy = highlightParams.createdBy {
HStack(alignment: .center) {
if let profileImageURL = createdBy.profileImageURL, let url = URL(string: profileImageURL) {
AsyncImage(
url: url,
content: { $0.resizable() },
placeholder: {
Image(systemName: "person.crop.circle")
.resizable()
.foregroundColor(.appGrayText)
}
)
.aspectRatio(contentMode: .fill)
.frame(width: 14, height: 14, alignment: .center)
.clipShape(Circle())
} else {
Image(systemName: "person.crop.circle")
.resizable()
.foregroundColor(.appGrayText)
.frame(width: 14, height: 14)
}
Text("Highlight by \(highlightParams.createdBy?.name ?? "you")")
.font(.appFootnote)
.foregroundColor(.appGrayText)
}
}
HStack {
Divider()
.frame(width: 2)
.overlay(Color.appYellow48)
.overlay(highlightParams.createdBy != nil ? Color(red: 206 / 255.0, green: 239 / 255.0, blue: 159 / 255.0) : Color.appYellow48)
.opacity(0.8)
.padding(.top, 2)
.padding(.trailing, 6)

View File

@ -11,6 +11,7 @@ struct HighlightListItemParams: Identifiable {
let annotation: String
let quote: String
let labels: [LinkedItemLabel]
let createdBy: InternalUserProfile?
}
@MainActor final class HighlightsListViewModel: ObservableObject {
@ -31,7 +32,8 @@ struct HighlightListItemParams: Identifiable {
title: highlightItems[index].title,
annotation: annotation,
quote: highlightItems[index].quote,
labels: highlightItems[index].labels
labels: highlightItems[index].labels,
createdBy: highlightItems[index].createdBy
)
}
}
@ -50,7 +52,8 @@ struct HighlightListItemParams: Identifiable {
title: highlightItems[index].title,
annotation: highlightItems[index].annotation,
quote: highlightItems[index].quote,
labels: labels
labels: labels,
createdBy: highlightItems[index].createdBy
)
}
}
@ -68,7 +71,8 @@ struct HighlightListItemParams: Identifiable {
title: "Highlight",
annotation: $0.annotation ?? "",
quote: $0.quote ?? "",
labels: $0.labels.asArray(of: LinkedItemLabel.self)
labels: $0.labels.asArray(of: LinkedItemLabel.self),
createdBy: $0.createdByMe ? nil : InternalUserProfile.makeSingle($0.createdBy)
)
}
}

View File

@ -127,7 +127,14 @@ struct RecommendationGroupView: View {
private var membersSection: some View {
Section("Members") {
if viewModel.nonAdmins.count > 0 {
if !viewModel.recommendationGroup.canSeeMembers {
Text("""
The admin of this group does not allow viewing all members.
[Learn more about groups](https://blog.omnivore.app/p/dca38ba4-8a74-42cc-90ca-d5ffa5d075cc)
""")
.accentColor(.blue)
} else if viewModel.nonAdmins.count > 0 {
ForEach(viewModel.nonAdmins) { member in
SmallUserCard(data: ProfileCardData(
name: member.name,

View File

@ -10,6 +10,8 @@ import Views
@Published var recommendationGroups = [InternalRecommendationGroup]()
@Published var showCreateSheet = false
@Published var newGroupOnlyAdminCanPost = false
@Published var newGroupOnlyAdminCanSeeMembers = false
@Published var showCreateError = false
@Published var createGroupError: String?
@ -30,7 +32,6 @@ import Views
isCreating = true
if let group = try? await dataService.createRecommendationGroup(name: name) {
print("CREATED GROUP: ", group)
await loadGroups(dataService: dataService)
showCreateSheet = false
} else {
@ -67,6 +68,11 @@ struct CreateRecommendationGroupView: View {
NavigationView {
Form {
TextField("Name", text: $name, prompt: Text("Group Name"))
Section {
Toggle("Only admins can post", isOn: $viewModel.newGroupOnlyAdminCanPost)
Toggle("Only admins can see members", isOn: $viewModel.newGroupOnlyAdminCanSeeMembers)
}
}
.alert(isPresented: $viewModel.showCreateError) {
Alert(

View File

@ -26,7 +26,7 @@ import Views
isLoading = true
do {
recommendationGroups = try await dataService.recommendationGroups()
recommendationGroups = try await dataService.recommendationGroups().filter(\.canPost)
} catch {
print("ERROR fetching recommendationGroups: ", error)
networkError = true
@ -120,7 +120,7 @@ struct RecommendToView: View {
if viewModel.highlightCount > 0 {
Toggle(isOn: $viewModel.withHighlights, label: {
HStack(alignment: .firstTextBaseline) {
Text("Include \(viewModel.highlightCount) highlight\(viewModel.highlightCount > 1 ? "s" : "")")
Text("Include your \(viewModel.highlightCount) highlight\(viewModel.highlightCount > 1 ? "s" : "")")
}
})
}
@ -139,24 +139,35 @@ struct RecommendToView: View {
EmptyView()
}
List {
Section("Select groups to recommend to") {
ForEach(viewModel.recommendationGroups) { group in
HStack {
Text(group.name)
if !viewModel.isLoading, viewModel.recommendationGroups.count < 1 {
Text("""
You do not have any groups you can post to.
Spacer()
Join a group or create your own to start recommending articles.
if viewModel.selectedGroups.contains(where: { $0.id == group.id }) {
Image(systemName: "checkmark")
[Learn more about groups](https://blog.omnivore.app/p/dca38ba4-8a74-42cc-90ca-d5ffa5d075cc)
""")
.accentColor(.blue)
} else {
Section("Select groups to recommend to") {
ForEach(viewModel.recommendationGroups) { group in
HStack {
Text(group.name)
Spacer()
if viewModel.selectedGroups.contains(where: { $0.id == group.id }) {
Image(systemName: "checkmark")
}
}
}
.contentShape(Rectangle())
.onTapGesture {
let idx = viewModel.selectedGroups.firstIndex(where: { $0.id == group.id })
if let idx = idx {
viewModel.selectedGroups.remove(at: idx)
} else {
viewModel.selectedGroups.append(group)
.contentShape(Rectangle())
.onTapGesture {
let idx = viewModel.selectedGroups.firstIndex(where: { $0.id == group.id })
if let idx = idx {
viewModel.selectedGroups.remove(at: idx)
} else {
viewModel.selectedGroups.append(group)
}
}
}
}

View File

@ -359,10 +359,12 @@ struct WebReaderContainerView: View {
})
}
.formSheet(isPresented: $showRecommendSheet) {
let highlightCount = item.highlights.asArray(of: Highlight.self).filter(\.createdByMe).count
NavigationView {
RecommendToView(
dataService: dataService,
viewModel: RecommendToViewModel(pageID: item.unwrappedID, highlightCount: item.highlights?.count ?? 0)
viewModel: RecommendToViewModel(pageID: item.unwrappedID,
highlightCount: highlightCount)
)
}.onDisappear {
showRecommendSheet = false

View File

@ -13,6 +13,7 @@
<attribute name="shortId" attributeType="String"/>
<attribute name="suffix" optional="YES" attributeType="String"/>
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="createdBy" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserProfile"/>
<relationship name="labels" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="LinkedItemLabel" inverseName="highlights" inverseEntity="LinkedItemLabel"/>
<relationship name="linkedItem" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LinkedItem" inverseName="highlights" inverseEntity="LinkedItem"/>
<uniquenessConstraints>
@ -93,7 +94,7 @@
<attribute name="term" optional="YES" attributeType="String"/>
</entity>
<entity name="Recommendation" representedClassName="Recommendation" syncable="YES" codeGenerationType="class">
<attribute name="id" optional="YES" attributeType="String"/>
<attribute name="groupID" optional="YES" attributeType="String"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="note" optional="YES" attributeType="String"/>
<attribute name="recommendedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
@ -101,6 +102,8 @@
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserProfile"/>
</entity>
<entity name="RecommendationGroup" representedClassName="RecommendationGroup" syncable="YES" codeGenerationType="class">
<attribute name="canPost" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="canSeeMembers" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="id" optional="YES" attributeType="String"/>
<attribute name="inviteUrl" optional="YES" attributeType="String"/>

View File

@ -133,7 +133,7 @@ public extension LinkedItem {
let recommendations = self.recommendations.asArray(of: Recommendation.self).map { recommendation in
let recommendedAt = recommendation.recommendedAt == nil ? nil : recommendation.recommendedAt?.ISO8601Format()
return [
"id": NSString(string: recommendation.id ?? ""),
"id": NSString(string: recommendation.groupID ?? ""),
"name": NSString(string: recommendation.name ?? ""),
"note": recommendation.note == nil ? nil : NSString(string: recommendation.note ?? ""),
"user": recommendation.user == nil ? nil : NSDictionary(dictionary: [

View File

@ -2,23 +2,6 @@ import CoreData
import Foundation
public extension Recommendation {
var unwrappedID: String { id ?? "" }
static func lookup(byID recommendationID: String, inContext context: NSManagedObjectContext) -> Recommendation? {
let fetchRequest: NSFetchRequest<Models.Recommendation> = Recommendation.fetchRequest()
fetchRequest.predicate = NSPredicate(
format: "id == %@", recommendationID
)
var recommendation: Recommendation?
context.performAndWait {
recommendation = (try? context.fetch(fetchRequest))?.first
}
return recommendation
}
static func byline(_ set: NSSet) -> String {
Array(set).reduce("") { str, item in
if let recommendation = item as? Recommendation, let userName = recommendation.user?.name {

View File

@ -4,6 +4,7 @@ public enum LinkedItemFilter: String, CaseIterable {
case inbox
case readlater
case newsletters
case recommended
case all
case archived
case hasHighlights
@ -19,6 +20,8 @@ public extension LinkedItemFilter {
return "Read Later"
case .newsletters:
return "Newsletters"
case .recommended:
return "Recommended"
case .all:
return "All"
case .archived:
@ -38,6 +41,8 @@ public extension LinkedItemFilter {
return "in:inbox -label:Newsletter"
case .newsletters:
return "in:inbox label:Newsletter"
case .recommended:
return "recommendedBy:*"
case .all:
return "in:all"
case .archived:
@ -75,6 +80,11 @@ public extension LinkedItemFilter {
format: "SUBQUERY(labels, $label, $label.name == \"Newsletter\").@count > 0"
)
return NSCompoundPredicate(andPredicateWithSubpredicates: [notInArchivePredicate, newsletterLabelPredicate])
case .recommended:
let recommendationsPredicate = NSPredicate(
format: "recommendations.@count > 0"
)
return NSCompoundPredicate(andPredicateWithSubpredicates: [notInArchivePredicate, recommendationsPredicate])
case .all:
// include everything undeleted
return undeletedPredicate

View File

@ -22,6 +22,7 @@ extension DataService {
createdAt: nil,
updatedAt: nil,
createdByMe: true,
createdBy: nil,
labels: []
)

View File

@ -24,6 +24,7 @@ extension DataService {
createdAt: nil,
updatedAt: nil,
createdByMe: true,
createdBy: nil,
labels: []
)

View File

@ -235,7 +235,7 @@ let recommendingUserSelection = Selection.RecommendingUser {
let recommendationSelection = Selection.Recommendation {
InternalRecommendation(
id: try $0.id(),
groupID: try $0.id(),
name: try $0.name(),
note: try $0.note(),
user: try $0.user(selection: recommendingUserSelection.nullable),

View File

@ -23,6 +23,7 @@ let highlightSelection = Selection.Highlight {
createdAt: try $0.createdAt().value,
updatedAt: try $0.updatedAt().value,
createdByMe: try $0.createdByMe(),
createdBy: try $0.user(selection: userProfileSelection),
labels: try $0.labels(selection: highlightLabelSelection.list.nullable) ?? []
)
}

View File

@ -6,6 +6,8 @@ let recommendationGroupSelection = Selection.RecommendationGroup {
id: try $0.id(),
name: try $0.name(),
inviteUrl: try $0.inviteUrl(),
canPost: true,
canSeeMembers: true,
admins: try $0.admins(selection: userProfileSelection.list),
members: try $0.members(selection: userProfileSelection.list)
)

View File

@ -13,6 +13,7 @@ struct InternalHighlight: Encodable {
let createdAt: Date?
let updatedAt: Date?
let createdByMe: Bool
let createdBy: InternalUserProfile?
var labels: [InternalLinkedItemLabel]
func asManagedObject(context: NSManagedObjectContext) -> Highlight {
@ -35,6 +36,10 @@ struct InternalHighlight: Encodable {
highlight.updatedAt = updatedAt
highlight.createdByMe = createdByMe
if let createdBy = createdBy {
highlight.createdBy = createdBy.asManagedObject(inContext: context)
}
if let existingLabels = highlight.labels {
highlight.removeFromLabels(existingLabels)
}
@ -58,6 +63,7 @@ struct InternalHighlight: Encodable {
createdAt: highlight.createdAt,
updatedAt: highlight.updatedAt,
createdByMe: highlight.createdByMe,
createdBy: InternalUserProfile.makeSingle(highlight.createdBy),
labels: InternalLinkedItemLabel.make(highlight.labels)
)
}

View File

@ -3,16 +3,16 @@ import Foundation
import Models
public struct InternalRecommendation {
let id: String
let groupID: String
let name: String
let note: String?
let user: InternalUserProfile?
let recommendedAt: Date
func asManagedObject(inContext context: NSManagedObjectContext) -> Recommendation {
let existing = Recommendation.lookup(byID: id, inContext: context)
let recommendation = existing ?? Recommendation(entity: Recommendation.entity(), insertInto: context)
recommendation.id = id
// let existing = Recommendation.lookup(byID: id, inContext: context)
let recommendation = /* existing ?? */ Recommendation(entity: Recommendation.entity(), insertInto: context)
recommendation.groupID = groupID
recommendation.name = name
recommendation.note = note
recommendation.recommendedAt = recommendedAt
@ -24,12 +24,12 @@ public struct InternalRecommendation {
recommendations?
.compactMap { recommendation in
if let recommendation = recommendation as? Recommendation,
let id = recommendation.id,
let groupID = recommendation.groupID,
let name = recommendation.name,
let recommendedAt = recommendation.recommendedAt
{
return InternalRecommendation(
id: id,
groupID: groupID,
name: name,
note: recommendation.note,
user: InternalUserProfile.makeSingle(recommendation.user),

View File

@ -13,6 +13,8 @@ public struct InternalRecommendationGroup: Identifiable {
public let id: String
public let name: String
public let inviteUrl: String
public let canPost: Bool
public let canSeeMembers: Bool
public let admins: [InternalUserProfile]
public let members: [InternalUserProfile]
@ -40,6 +42,8 @@ public struct InternalRecommendationGroup: Identifiable {
id: id,
name: name,
inviteUrl: inviteUrl,
canPost: recommendationGroup.canPost,
canSeeMembers: recommendationGroup.canSeeMembers,
admins: InternalUserProfile.make(recommendationGroup.admins),
members: InternalUserProfile.make(recommendationGroup.members)
)

View File

@ -2,7 +2,7 @@ import CoreData
import Foundation
import Models
public struct InternalUserProfile: Identifiable {
public struct InternalUserProfile: Identifiable, Encodable {
let userID: String
public let name: String
public let username: String

View File

@ -0,0 +1,38 @@
{
"colors": [
{
"color": {
"color-space": "srgb",
"components": {
"alpha": "1.000",
"blue": "0xE5",
"green": "0xFF",
"red": "0xE5"
}
},
"idiom": "universal"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"color": {
"color-space": "srgb",
"components": {
"alpha": "1.000",
"blue": "0xE5",
"green": "0xFF",
"red": "0xE5"
}
},
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

View File

@ -1,38 +1,38 @@
{
"colors" : [
"colors": [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x13",
"green" : "0xB5",
"red" : "0xE2"
"color": {
"color-space": "srgb",
"components": {
"alpha": "1.000",
"blue": "0x92",
"green": "0xe3",
"red": "0xfa"
}
},
"idiom" : "universal"
"idiom": "universal"
},
{
"appearances" : [
"appearances": [
{
"appearance" : "luminosity",
"value" : "dark"
"appearance": "luminosity",
"value": "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x13",
"green" : "0xB5",
"red" : "0xE2"
"color": {
"color-space": "srgb",
"components": {
"alpha": "1.000",
"blue": "0x92",
"green": "0xe3",
"red": "0xfa"
}
},
"idiom" : "universal"
"idiom": "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
"info": {
"author": "xcode",
"version": 1
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -5,18 +5,24 @@ type AvatarProps = {
imageURL?: string
height: string
fallbackText: string
tooltip?: string
noFade?: boolean
}
export function Avatar(props: AvatarProps): JSX.Element {
return (
<StyledAvatar
title={props.tooltip}
css={{
width: props.height,
height: props.height,
borderRadius: '50%',
}}
>
<StyledImage src={props.imageURL} />
<StyledImage
src={props.imageURL}
css={{ opacity: props.noFade ? 'unset' : '48%' }}
/>
<StyledFallback>{props.fallbackText}</StyledFallback>
</StyledAvatar>
)
@ -36,7 +42,6 @@ const StyledImage = styled(Image, {
width: '100%',
height: '100%',
objectFit: 'cover',
opacity: '48%',
'&:hover': {
opacity: '100%',

View File

@ -23,12 +23,12 @@ const textVariants = {
lineHeight: '1.25',
},
recommendedByline: {
// fontWeight: 'bold',
fontWeight: 'bold',
fontSize: '13.5px',
paddingTop: '4px',
mt: '0px',
mb: '24px',
color: '$grayText',
mb: '16px',
color: '$grayTextContrast',
},
userName: {
fontWeight: '600',

View File

@ -7,7 +7,7 @@ import {
SpanBox,
VStack,
} from './../../elements/LayoutPrimitives'
import { StyledText } from './../../elements/StyledText'
import { StyledText, StyledTextSpan } from './../../elements/StyledText'
import { ArticleSubtitle } from './../../patterns/ArticleSubtitle'
import { styled, theme, ThemeId } from './../../tokens/stitches.config'
import { HighlightsLayer } from '../../templates/article/HighlightsLayer'
@ -215,14 +215,6 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
readerHeadersColor: theme.colors.readerHeader.toString(),
}
const recommendationByline = useMemo(() => {
return props.article.recommendations
?.flatMap((recommendation) => {
return recommendation.user?.name
})
.join(', ')
}, [props.article.recommendations])
const recommendationsWithNotes = useMemo(() => {
return (
props.article.recommendations?.filter((recommendation) => {
@ -310,40 +302,60 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
))}
</SpanBox>
) : null}
{recommendationByline && (
{recommendationsWithNotes.length > 0 && (
<VStack
id="recommendations-container"
css={{
borderRadius: '6px',
bg: '$grayBase',
bg: '$grayBgSubtle',
p: '16px',
pt: '16px',
pb: '2px',
width: '100%',
marginTop: '24px',
color: '$grayText',
lineHeight: '2.0',
}}
>
<HStack css={{ gap: '8px' }}>
<Sparkle size="14" />
<HStack css={{ pb: '0px', mb: '0px' }}>
<StyledText
style="recommendedByline"
css={{ paddingTop: '0px' }}
css={{ paddingTop: '0px', mb: '16px' }}
>
Recommended by {recommendationByline}
Comments{' '}
<SpanBox css={{ color: 'grayText', fontWeight: '400' }}>
&nbsp;{` ${recommendationsWithNotes.length}`}
</SpanBox>
</StyledText>
</HStack>
{recommendationsWithNotes.map((item, idx) => (
<VStack key={item.id} alignment="start" distribution="start">
<VStack
key={item.id}
alignment="start"
distribution="start"
css={{ pt: '0px', pb: '8px' }}
>
{/* <StyledQuote>{item.note}</StyledQuote> */}
<HStack css={{}} alignment="start">
<StyledText style="userNote">
<SpanBox css={{ opacity: '0.5' }}>
{item.user?.name}:
</SpanBox>{' '}
<HStack>
<SpanBox
css={{
verticalAlign: 'top',
minWidth: '28px',
display: 'flex',
}}
>
<Avatar
imageURL={item.user?.profileImageURL}
height="28px"
noFade={true}
tooltip={item.user?.name}
fallbackText={item.user?.username[0] ?? 'U'}
/>
</SpanBox>
<StyledText style="userNote" css={{ pl: '16px' }}>
{item.note}
</StyledText>
:
</HStack>
</VStack>
))}

View File

@ -7,7 +7,7 @@ export enum ThemeId {
Dark = 'Gray',
Darker = 'Dark',
Sepia = 'Sepia',
Charcoal = 'Charcoal'
Charcoal = 'Charcoal',
}
export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
@ -132,6 +132,7 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
// Semantic Colors
highlightBackground: '250, 227, 146',
recommendedHighlightBackground: '#E5FFE5',
highlight: '#FFD234',
highlightText: '#3D3D3D',
error: '#FA5E4A',
@ -165,7 +166,6 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
libraryActiveMenuItem: '#F8F8F8',
border: '#F0F0F0',
//utility
textNonEssential: 'rgba(10, 8, 6, 0.4)',
overlay: 'rgba(63, 62, 60, 0.2)',
@ -205,6 +205,7 @@ const darkThemeSpec = {
// Semantic Colors
highlightBackground: '134, 119, 64',
recommendedHighlightBackground: '#1F4315',
highlight: '#FFD234',
highlightText: 'white',
error: '#FA5E4A',
@ -245,7 +246,7 @@ const sepiaThemeSpec = {
readerFontHighContrast: 'black',
readerHeader: '554A34',
readerTableHeader: '#FFFFFF',
}
},
}
const charcoalThemeSpec = {
@ -256,16 +257,21 @@ const charcoalThemeSpec = {
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})
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
@ -273,8 +279,8 @@ export const lighterTheme = createTheme(ThemeId.Lighter, {})
// Apply global styles in here
export const globalStyles = globalCss({
'body': {
backgroundColor: '$grayBase'
body: {
backgroundColor: '$grayBase',
},
'*': {
'&:focus': {

View File

@ -67,7 +67,7 @@ function nodeAttributesFromHighlight(
const patch = highlight.patch
const id = highlight.id
const withNote = !!highlight.annotation
const customColor = undefined
var customColor = undefined
const tooltip = undefined
// We've disabled shared highlights, so passing undefined
// here now, and removing the user object from highlights
@ -78,6 +78,10 @@ function nodeAttributesFromHighlight(
// ? `Created by: @${highlight.user.profile.username}`
// : undefined
if (!highlight.createdByMe) {
customColor = 'var(--colors-recommendedHighlightBackground)'
}
return makeHighlightNodeAttributes(patch, id, withNote, customColor, tooltip)
}

View File

@ -184,6 +184,9 @@ async function makeSelectionRange(): Promise<
}
const articleContentElement = document.getElementById('article-container')
const recommendationsElement = document.getElementById(
'recommendations-container'
)
if (!articleContentElement)
throw new Error('Unable to find the article content element')
@ -193,6 +196,11 @@ async function makeSelectionRange(): Promise<
const range = selection.getRangeAt(0)
if (recommendationsElement && range.intersectsNode(recommendationsElement)) {
console.log('attempt to highlight in recommendations area')
return undefined
}
const start = range.compareBoundaryPoints(Range.START_TO_START, allowedRange)
const end = range.compareBoundaryPoints(Range.END_TO_END, allowedRange)
const isRangeAllowed = start >= 0 && end <= 0

View File

@ -47,6 +47,7 @@ export const labelColorObjects: LabelColorObjects = {
export const randomLabelColorHex = () => {
const colorHexes = Object.keys(labelColorObjects).slice(0, -1)
const randomColorHex = colorHexes[Math.floor(Math.random() * colorHexes.length)]
const randomColorHex =
colorHexes[Math.floor(Math.random() * colorHexes.length)]
return randomColorHex
}
}

View File

@ -1,3 +1,3 @@
OMNIVORE_URL="https://omnivore.app"
OMNIVORE_GRAPHQL_URL="https://api-prod.omnivore.app/api/"
EXTENSION_NAME="Omnivore"
EXTENSION_NAME="Omnivore: Read-it-later for serious readers"

View File

@ -2,8 +2,8 @@
"manifest_version": 2,
"name": "process.env.EXTENSION_NAME",
"short_name": "process.env.EXTENSION_NAME",
"version": "0.1.26",
"description": "Save articles to your Omnivore library",
"version": "0.1.28",
"description": "Read-it-later for serious readers",
"author": "Omnivore Media, Inc",
"default_locale": "en",
"developer": {
@ -21,14 +21,17 @@
"128": "/images/extension/icon-128.png",
"256": "/images/extension/icon-256.png"
},
"permissions": ["activeTab", "storage", "contextMenus", "https://*/**", "http://*/**"],
"permissions": [
"activeTab",
"storage",
"contextMenus",
"https://*/**",
"http://*/**"
],
"background": {
"page": "/views/background.html",
"persistent": true
},
"minimum_chrome_version": "21",
"minimum_opera_version": "15",
"applications": {
@ -41,10 +44,12 @@
"id": "save-extension@omnivore.app"
}
},
"content_scripts": [
{
"matches": ["https://*/**", "http://*/**"],
"matches": [
"https://*/**",
"http://*/**"
],
"js": [
"/scripts/constants.js",
"/scripts/content/page-info.js",
@ -54,12 +59,16 @@
]
},
{
"matches": ["https://*/**", "http://*/**"],
"js": ["/scripts/content/grab-iframe-content.js"],
"matches": [
"https://*/**",
"http://*/**"
],
"js": [
"/scripts/content/grab-iframe-content.js"
],
"all_frames": true
}
],
"browser_action": {
"default_icon": {
"16": "/images/toolbar/icon-16.png",
@ -69,17 +78,17 @@
"38": "/images/toolbar/icon-38.png",
"48": "/images/toolbar/icon-48.png"
},
"default_title": "Omnivore Save Article"
"default_title": "Omnivore: Read-it-later for serious readers"
},
"commands": {
"_execute_browser_action": {
"suggested_key": {
"default": "Alt + O"
},
"description": "Save the current tab to Omnivore"
"suggested_key": {
"default": "Alt + O"
},
"description": "Save the current tab to Omnivore"
}
},
"web_accessible_resources": ["views/cta-popup.html"]
}
"web_accessible_resources": [
"views/cta-popup.html"
]
}