mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Merge branch 'main' into ios-ui-settings-localised
# Conflicts: # iOS/Settings/Settings.storyboard # iOS/Settings/SettingsViewController.swift
This commit is contained in:
@@ -11,7 +11,7 @@ import Foundation
|
||||
// Threading rules:
|
||||
// * Main-thread only
|
||||
// * Except: may be created on background thread by StatusesTable.
|
||||
// Which is safe, because at creation time it’t not yet shared,
|
||||
// Which is safe, because at creation time it’s not yet shared,
|
||||
// and it won’t be mutated ever on a background thread.
|
||||
|
||||
public final class ArticleStatus: Hashable {
|
||||
|
||||
@@ -44,6 +44,7 @@ final class AppDefaults {
|
||||
static let currentThemeName = "currentThemeName"
|
||||
static let hasSeenNotAllArticlesHaveURLsAlert = "hasSeenNotAllArticlesHaveURLsAlert"
|
||||
static let twitterDeprecationAlertShown = "twitterDeprecationAlertShown"
|
||||
static let markArticlesAsReadOnScroll = "markArticlesAsReadOnScroll"
|
||||
|
||||
// Hidden prefs
|
||||
static let showDebugMenu = "ShowDebugMenu"
|
||||
@@ -329,6 +330,14 @@ final class AppDefaults {
|
||||
}
|
||||
}
|
||||
|
||||
var markArticlesAsReadOnScroll: Bool {
|
||||
get {
|
||||
return AppDefaults.bool(for: Key.markArticlesAsReadOnScroll)
|
||||
}
|
||||
set {
|
||||
AppDefaults.setBool(for: Key.markArticlesAsReadOnScroll, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
func registerDefaults() {
|
||||
#if DEBUG
|
||||
|
||||
@@ -32,22 +32,30 @@
|
||||
<objects>
|
||||
<viewController title="General" storyboardIdentifier="General" id="iuH-lz-18x" customClass="GeneralPreferencesViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="WnV-px-wCT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="509" height="338"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="509" height="361"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Ut3-yd-q6G">
|
||||
<rect key="frame" x="53" y="16" width="402" height="306"/>
|
||||
<rect key="frame" x="54" y="16" width="399" height="329"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pR2-Bf-7Fd">
|
||||
<rect key="frame" x="6" y="285" width="106" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Article Text Size:" id="xQu-QV-91i">
|
||||
<rect key="frame" x="6" y="267" width="105" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Article Text Size:" id="xQu-QV-91i">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4AW-o5-47e">
|
||||
<rect key="frame" x="52" y="309" width="59" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Timeline:" id="wi9-yM-Ri0">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Z6O-Zt-V1g">
|
||||
<rect key="frame" x="115" y="278" width="289" height="25"/>
|
||||
<rect key="frame" x="114" y="260" width="289" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Medium" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="proportionallyDown" inset="2" selectedItem="jMV-2o-5Oh" id="6pw-Vq-tjM">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -76,7 +84,7 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ISO-Wu-R60">
|
||||
<rect key="frame" x="115" y="244" width="289" height="25"/>
|
||||
<rect key="frame" x="114" y="226" width="289" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Default" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Pkl-EA-Goa" id="vN9-pm-Gls">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -91,7 +99,7 @@
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1w0-nA-DEO">
|
||||
<rect key="frame" x="111" y="207" width="161" height="32"/>
|
||||
<rect key="frame" x="110" y="189" width="161" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Open Themes Folder" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ySX-5i-SP1">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -101,18 +109,18 @@
|
||||
</connections>
|
||||
</button>
|
||||
<box verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="Tdg-6Y-gvW">
|
||||
<rect key="frame" x="0.0" y="197" width="402" height="5"/>
|
||||
<rect key="frame" x="0.0" y="181" width="399" height="5"/>
|
||||
</box>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Wsb-Lr-8Q7">
|
||||
<rect key="frame" x="6" y="166" width="106" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="right" title="Browser:" id="CgU-dE-Qtb">
|
||||
<rect key="frame" x="53" y="154" width="58" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Browser:" id="CgU-dE-Qtb">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ci4-fW-KjU">
|
||||
<rect key="frame" x="115" y="159" width="289" height="25"/>
|
||||
<rect key="frame" x="114" y="147" width="289" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Safari" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="ObP-qK-qDJ" id="hrm-aT-Rc2" userLabel="Popup">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -127,7 +135,7 @@
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<button horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Ubm-Pk-l7x">
|
||||
<rect key="frame" x="116" y="132" width="284" height="18"/>
|
||||
<rect key="frame" x="115" y="122" width="284" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Open web pages in background in browser" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="t0a-LN-rCv">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -147,7 +155,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="j0t-Wa-UTL">
|
||||
<rect key="frame" x="135" y="109" width="235" height="16"/>
|
||||
<rect key="frame" x="134" y="99" width="235" height="16"/>
|
||||
<textFieldCell key="cell" controlSize="small" title="Press the Shift key to do the opposite." id="EMq-9M-zTJ">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -155,18 +163,18 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<box verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="hQy-ng-ijd">
|
||||
<rect key="frame" x="0.0" y="38" width="402" height="5"/>
|
||||
<rect key="frame" x="0.0" y="34" width="399" height="5"/>
|
||||
</box>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ucw-vG-yLt">
|
||||
<rect key="frame" x="6" y="7" width="106" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="right" title="Refresh Feeds:" id="F7c-lm-g97">
|
||||
<rect key="frame" x="16" y="7" width="95" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Refresh Feeds:" id="F7c-lm-g97">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SFF-mL-yc8">
|
||||
<rect key="frame" x="115" y="0.0" width="289" height="25"/>
|
||||
<rect key="frame" x="114" y="0.0" width="289" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="200" id="N1a-qV-4Os"/>
|
||||
</constraints>
|
||||
@@ -201,15 +209,15 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="yrg-M3-Dbz">
|
||||
<rect key="frame" x="6" y="79" width="106" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="right" title="Safari Extension:" id="Eth-o0-pWM">
|
||||
<rect key="frame" x="7" y="71" width="106" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Safari Extension:" id="Eth-o0-pWM">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="wtY-Zd-Ps9">
|
||||
<rect key="frame" x="116" y="78" width="284" height="18"/>
|
||||
<rect key="frame" x="115" y="70" width="284" height="18"/>
|
||||
<buttonCell key="cell" type="radio" title="Open feeds in NetNewsWire" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="uvx-O8-HvU">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -223,7 +231,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Yrc-6Q-kx8">
|
||||
<rect key="frame" x="116" y="56" width="284" height="18"/>
|
||||
<rect key="frame" x="115" y="48" width="284" height="18"/>
|
||||
<buttonCell key="cell" type="radio" title="Open feeds in default news reader" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="SkZ-tE-blK">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -233,75 +241,95 @@
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="S2Z-bG-jYk">
|
||||
<rect key="frame" x="6" y="251" width="106" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Article Theme:" id="MQe-Za-N8J">
|
||||
<rect key="frame" x="18" y="233" width="93" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Article Theme:" id="MQe-Za-N8J">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="qmW-lo-ERe">
|
||||
<rect key="frame" x="115" y="308" width="208" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Mark articles as read on scroll" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="ANv-PZ-pn6">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<binding destination="mAF-gO-1PI" name="value" keyPath="values.markArticlesAsReadOnScroll" id="75p-kw-A3m">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
<bool key="NSConditionallySetsEnabled" value="NO"/>
|
||||
<integer key="NSMultipleValuesPlaceholder" value="0"/>
|
||||
<integer key="NSNoSelectionPlaceholder" value="0"/>
|
||||
<integer key="NSNotApplicablePlaceholder" value="0"/>
|
||||
<integer key="NSNullPlaceholder" value="0"/>
|
||||
<bool key="NSRaisesForNotApplicableKeys" value="NO"/>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</button>
|
||||
<box verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="cXT-QS-qRf">
|
||||
<rect key="frame" x="0.0" y="294" width="399" height="5"/>
|
||||
</box>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Z6O-Zt-V1g" firstAttribute="top" secondItem="cXT-QS-qRf" secondAttribute="bottom" constant="12" id="04m-cu-OZo"/>
|
||||
<constraint firstItem="Wsb-Lr-8Q7" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="17A-5m-ZG0"/>
|
||||
<constraint firstItem="Z6O-Zt-V1g" firstAttribute="leading" secondItem="pR2-Bf-7Fd" secondAttribute="trailing" constant="8" symbolic="YES" id="2wM-K6-eAF"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Ubm-Pk-l7x" secondAttribute="trailing" id="3h4-m7-pMW"/>
|
||||
<constraint firstItem="cXT-QS-qRf" firstAttribute="leading" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="4zW-Zw-kDb"/>
|
||||
<constraint firstItem="Yrc-6Q-kx8" firstAttribute="top" secondItem="wtY-Zd-Ps9" secondAttribute="bottom" constant="6" symbolic="YES" id="59s-XY-cPN"/>
|
||||
<constraint firstItem="Wsb-Lr-8Q7" firstAttribute="width" secondItem="pR2-Bf-7Fd" secondAttribute="width" id="5IG-Sz-w9C"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="j0t-Wa-UTL" secondAttribute="trailing" id="7Oh-pf-X12"/>
|
||||
<constraint firstItem="Ubm-Pk-l7x" firstAttribute="leading" secondItem="SFF-mL-yc8" secondAttribute="leading" id="7cy-O4-Zz2"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="1w0-nA-DEO" secondAttribute="trailing" constant="20" symbolic="YES" id="7mc-lc-2SG"/>
|
||||
<constraint firstItem="yrg-M3-Dbz" firstAttribute="width" secondItem="pR2-Bf-7Fd" secondAttribute="width" id="7pB-AA-oez"/>
|
||||
<constraint firstItem="yrg-M3-Dbz" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="8KD-aE-Zer"/>
|
||||
<constraint firstItem="Ci4-fW-KjU" firstAttribute="width" secondItem="SFF-mL-yc8" secondAttribute="width" id="AE4-am-IWK"/>
|
||||
<constraint firstItem="wtY-Zd-Ps9" firstAttribute="firstBaseline" secondItem="yrg-M3-Dbz" secondAttribute="baseline" id="AeO-w1-7yq"/>
|
||||
<constraint firstItem="1w0-nA-DEO" firstAttribute="leading" secondItem="Ci4-fW-KjU" secondAttribute="leading" id="FZM-a2-She"/>
|
||||
<constraint firstItem="Ubm-Pk-l7x" firstAttribute="top" secondItem="Ci4-fW-KjU" secondAttribute="bottom" constant="14" id="GNx-7d-yAo"/>
|
||||
<constraint firstItem="Wsb-Lr-8Q7" firstAttribute="trailing" secondItem="pR2-Bf-7Fd" secondAttribute="trailing" id="GRf-bX-t7o"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="qmW-lo-ERe" secondAttribute="trailing" id="CRn-3q-DHe"/>
|
||||
<constraint firstItem="Ubm-Pk-l7x" firstAttribute="top" secondItem="Ci4-fW-KjU" secondAttribute="bottom" constant="12" id="GNx-7d-yAo"/>
|
||||
<constraint firstItem="ISO-Wu-R60" firstAttribute="leading" secondItem="Z6O-Zt-V1g" secondAttribute="leading" id="GxL-2l-CYb"/>
|
||||
<constraint firstItem="4AW-o5-47e" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="HgL-ri-piv"/>
|
||||
<constraint firstItem="qmW-lo-ERe" firstAttribute="leading" secondItem="4AW-o5-47e" secondAttribute="trailing" constant="8" symbolic="YES" id="Ino-V2-58K"/>
|
||||
<constraint firstItem="hQy-ng-ijd" firstAttribute="leading" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="KEI-R5-rzD"/>
|
||||
<constraint firstItem="S2Z-bG-jYk" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="KQI-3T-s6M"/>
|
||||
<constraint firstItem="4AW-o5-47e" firstAttribute="firstBaseline" secondItem="qmW-lo-ERe" secondAttribute="firstBaseline" id="KdH-x0-OD4"/>
|
||||
<constraint firstItem="pR2-Bf-7Fd" firstAttribute="leading" secondItem="Ut3-yd-q6G" secondAttribute="leading" constant="8" id="LRG-HZ-yxh"/>
|
||||
<constraint firstAttribute="trailing" secondItem="SFF-mL-yc8" secondAttribute="trailing" constant="2" id="N39-Q9-X5Q"/>
|
||||
<constraint firstItem="Wsb-Lr-8Q7" firstAttribute="trailing" secondItem="pR2-Bf-7Fd" secondAttribute="trailing" id="Ore-Y8-DM8"/>
|
||||
<constraint firstAttribute="trailing" secondItem="SFF-mL-yc8" secondAttribute="trailing" id="N39-Q9-X5Q"/>
|
||||
<constraint firstItem="ISO-Wu-R60" firstAttribute="trailing" secondItem="Z6O-Zt-V1g" secondAttribute="trailing" id="P3r-hD-nE8"/>
|
||||
<constraint firstItem="S2Z-bG-jYk" firstAttribute="width" secondItem="pR2-Bf-7Fd" secondAttribute="width" id="QCg-QQ-rJf"/>
|
||||
<constraint firstItem="ISO-Wu-R60" firstAttribute="leading" secondItem="S2Z-bG-jYk" secondAttribute="trailing" constant="8" symbolic="YES" id="QDj-xS-6Ox"/>
|
||||
<constraint firstItem="S2Z-bG-jYk" firstAttribute="trailing" secondItem="pR2-Bf-7Fd" secondAttribute="trailing" id="QF9-uC-T7a"/>
|
||||
<constraint firstAttribute="trailing" secondItem="hQy-ng-ijd" secondAttribute="trailing" id="RbT-jK-fBb"/>
|
||||
<constraint firstItem="ucw-vG-yLt" firstAttribute="trailing" secondItem="pR2-Bf-7Fd" secondAttribute="trailing" id="S1Y-iY-pca"/>
|
||||
<constraint firstItem="Z6O-Zt-V1g" firstAttribute="top" secondItem="Ut3-yd-q6G" secondAttribute="top" constant="4" id="SKa-kI-qdW"/>
|
||||
<constraint firstItem="Ubm-Pk-l7x" firstAttribute="width" secondItem="SFF-mL-yc8" secondAttribute="width" id="TX4-iO-J5E"/>
|
||||
<constraint firstItem="j0t-Wa-UTL" firstAttribute="leading" secondItem="Ubm-Pk-l7x" secondAttribute="leading" constant="19" id="UKq-8p-lyR"/>
|
||||
<constraint firstItem="j0t-Wa-UTL" firstAttribute="top" secondItem="Ubm-Pk-l7x" secondAttribute="bottom" constant="8" id="XTw-Ef-FD3"/>
|
||||
<constraint firstItem="wtY-Zd-Ps9" firstAttribute="trailing" secondItem="SFF-mL-yc8" secondAttribute="trailing" id="Zkn-zv-as5"/>
|
||||
<constraint firstItem="1w0-nA-DEO" firstAttribute="top" secondItem="ISO-Wu-R60" secondAttribute="bottom" constant="14" id="ZlG-V3-AAd"/>
|
||||
<constraint firstItem="pR2-Bf-7Fd" firstAttribute="firstBaseline" secondItem="Z6O-Zt-V1g" secondAttribute="firstBaseline" id="aO5-iE-L7A"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Z6O-Zt-V1g" secondAttribute="trailing" constant="2" id="aS9-KA-vSH"/>
|
||||
<constraint firstItem="wtY-Zd-Ps9" firstAttribute="top" secondItem="j0t-Wa-UTL" secondAttribute="bottom" constant="14" id="aod-td-Gim"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Z6O-Zt-V1g" secondAttribute="trailing" id="aS9-KA-vSH"/>
|
||||
<constraint firstItem="qmW-lo-ERe" firstAttribute="leading" secondItem="Z6O-Zt-V1g" secondAttribute="leading" id="aaj-KG-JNC"/>
|
||||
<constraint firstItem="wtY-Zd-Ps9" firstAttribute="top" secondItem="j0t-Wa-UTL" secondAttribute="bottom" constant="12" id="aod-td-Gim"/>
|
||||
<constraint firstItem="SFF-mL-yc8" firstAttribute="firstBaseline" secondItem="ucw-vG-yLt" secondAttribute="firstBaseline" id="aqn-St-DJy"/>
|
||||
<constraint firstItem="Tdg-6Y-gvW" firstAttribute="leading" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="b3I-JF-If3"/>
|
||||
<constraint firstItem="ucw-vG-yLt" firstAttribute="width" secondItem="pR2-Bf-7Fd" secondAttribute="width" id="cxz-v5-yCz"/>
|
||||
<constraint firstItem="wtY-Zd-Ps9" firstAttribute="leading" secondItem="SFF-mL-yc8" secondAttribute="leading" id="dTq-cu-Z2s"/>
|
||||
<constraint firstItem="Yrc-6Q-kx8" firstAttribute="trailing" secondItem="wtY-Zd-Ps9" secondAttribute="trailing" id="e6V-q6-WJq"/>
|
||||
<constraint firstItem="SFF-mL-yc8" firstAttribute="top" secondItem="hQy-ng-ijd" secondAttribute="bottom" constant="16" id="eM7-OM-Qsz"/>
|
||||
<constraint firstItem="SFF-mL-yc8" firstAttribute="top" secondItem="hQy-ng-ijd" secondAttribute="bottom" constant="12" id="eM7-OM-Qsz"/>
|
||||
<constraint firstItem="cXT-QS-qRf" firstAttribute="top" secondItem="4AW-o5-47e" secondAttribute="bottom" constant="12" id="eQ9-Mk-aYj"/>
|
||||
<constraint firstItem="Yrc-6Q-kx8" firstAttribute="leading" secondItem="wtY-Zd-Ps9" secondAttribute="leading" id="gNX-Yc-DdD"/>
|
||||
<constraint firstItem="1w0-nA-DEO" firstAttribute="leading" secondItem="ISO-Wu-R60" secondAttribute="leading" id="gWR-OU-qcO"/>
|
||||
<constraint firstItem="Ci4-fW-KjU" firstAttribute="top" secondItem="Tdg-6Y-gvW" secondAttribute="bottom" constant="16" id="hXl-1D-lTD"/>
|
||||
<constraint firstItem="hQy-ng-ijd" firstAttribute="top" secondItem="Yrc-6Q-kx8" secondAttribute="bottom" constant="16" id="i2g-cZ-EV4"/>
|
||||
<constraint firstItem="SFF-mL-yc8" firstAttribute="leading" secondItem="Ci4-fW-KjU" secondAttribute="leading" id="jfK-1b-DYd"/>
|
||||
<constraint firstAttribute="trailing" secondItem="cXT-QS-qRf" secondAttribute="trailing" id="hGN-Qp-fMS"/>
|
||||
<constraint firstItem="Ci4-fW-KjU" firstAttribute="top" secondItem="Tdg-6Y-gvW" secondAttribute="bottom" constant="12" id="hXl-1D-lTD"/>
|
||||
<constraint firstItem="wtY-Zd-Ps9" firstAttribute="leading" secondItem="yrg-M3-Dbz" secondAttribute="trailing" constant="6" symbolic="YES" id="hpP-sx-veV"/>
|
||||
<constraint firstItem="hQy-ng-ijd" firstAttribute="top" secondItem="Yrc-6Q-kx8" secondAttribute="bottom" constant="12" id="i2g-cZ-EV4"/>
|
||||
<constraint firstItem="ucw-vG-yLt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="lDL-JN-ANP"/>
|
||||
<constraint firstItem="Tdg-6Y-gvW" firstAttribute="top" secondItem="1w0-nA-DEO" secondAttribute="bottom" constant="14" id="lEK-yl-TCM"/>
|
||||
<constraint firstItem="Tdg-6Y-gvW" firstAttribute="top" secondItem="1w0-nA-DEO" secondAttribute="bottom" constant="12" id="lEK-yl-TCM"/>
|
||||
<constraint firstItem="Z6O-Zt-V1g" firstAttribute="width" secondItem="SFF-mL-yc8" secondAttribute="width" id="noW-Jf-Xbs"/>
|
||||
<constraint firstItem="qmW-lo-ERe" firstAttribute="top" secondItem="Ut3-yd-q6G" secondAttribute="top" constant="4" id="oto-bS-L2S"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Tdg-6Y-gvW" secondAttribute="trailing" id="qzz-gu-8kO"/>
|
||||
<constraint firstItem="Wsb-Lr-8Q7" firstAttribute="firstBaseline" secondItem="Ci4-fW-KjU" secondAttribute="firstBaseline" id="rPX-je-OG5"/>
|
||||
<constraint firstItem="Ci4-fW-KjU" firstAttribute="leading" secondItem="Wsb-Lr-8Q7" secondAttribute="trailing" constant="8" symbolic="YES" id="rcx-B6-zLP"/>
|
||||
<constraint firstItem="yrg-M3-Dbz" firstAttribute="trailing" secondItem="pR2-Bf-7Fd" secondAttribute="trailing" id="wHt-nz-9FO"/>
|
||||
<constraint firstItem="S2Z-bG-jYk" firstAttribute="firstBaseline" secondItem="ISO-Wu-R60" secondAttribute="firstBaseline" id="xt6-ua-xz8"/>
|
||||
<constraint firstItem="SFF-mL-yc8" firstAttribute="leading" secondItem="ucw-vG-yLt" secondAttribute="trailing" constant="8" symbolic="YES" id="yBm-Dc-lGA"/>
|
||||
<constraint firstAttribute="bottom" secondItem="SFF-mL-yc8" secondAttribute="bottom" constant="4" id="zIa-Ca-y3J"/>
|
||||
<constraint firstItem="ISO-Wu-R60" firstAttribute="top" secondItem="Z6O-Zt-V1g" secondAttribute="bottom" constant="14" id="zaM-J3-VcP"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Ci4-fW-KjU" secondAttribute="trailing" constant="2" id="zbx-Ch-NEt"/>
|
||||
<constraint firstItem="ucw-vG-yLt" firstAttribute="trailing" secondItem="pR2-Bf-7Fd" secondAttribute="trailing" id="zkC-Ma-Dz8"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Ci4-fW-KjU" secondAttribute="trailing" id="zbx-Ch-NEt"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
</subviews>
|
||||
@@ -427,35 +455,40 @@
|
||||
</binding>
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="OSS-A0-uUS" customClass="LinkTextField" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="121" y="1" width="88" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Privacy Policy" allowsEditingTextAttributes="YES" id="rJu-r1-AW4">
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uuc-f2-OFX">
|
||||
<rect key="frame" x="84" y="-6" width="148" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Privacy Policy" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="kSv-Wu-NYx">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="linkColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="showPrivacyPolicy:" target="VX1-M3-K0J" id="s1x-cP-hGd"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="MzL-QQ-2oL" secondAttribute="trailing" id="04t-Su-3fv"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="T4A-0o-p2w" secondAttribute="trailing" id="0zv-Cr-GlR"/>
|
||||
<constraint firstItem="uuc-f2-OFX" firstAttribute="width" secondItem="TKI-a9-bRX" secondAttribute="width" id="4ZH-zo-sNF"/>
|
||||
<constraint firstItem="QCu-J4-0yV" firstAttribute="leading" secondItem="T4A-0o-p2w" secondAttribute="leading" id="4c4-16-5yq"/>
|
||||
<constraint firstItem="TKI-a9-bRX" firstAttribute="leading" secondItem="CeE-AE-hRG" secondAttribute="leading" id="6Sm-VV-Qda"/>
|
||||
<constraint firstItem="EH5-aS-E55" firstAttribute="leading" secondItem="uJD-OF-YVY" secondAttribute="leading" id="6eS-X9-PTK"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="UHg-1l-FlD" secondAttribute="trailing" id="7gl-UP-wqg"/>
|
||||
<constraint firstAttribute="bottom" secondItem="OSS-A0-uUS" secondAttribute="bottom" constant="1" id="BFY-9B-ITb"/>
|
||||
<constraint firstItem="UHg-1l-FlD" firstAttribute="firstBaseline" secondItem="SUN-k3-ZEb" secondAttribute="firstBaseline" id="MAL-Ip-mEN"/>
|
||||
<constraint firstItem="MzL-QQ-2oL" firstAttribute="leading" secondItem="uJD-OF-YVY" secondAttribute="leading" id="MMt-v0-0gl"/>
|
||||
<constraint firstItem="CeE-AE-hRG" firstAttribute="leading" secondItem="T4A-0o-p2w" secondAttribute="leading" id="NWB-BO-GtL"/>
|
||||
<constraint firstItem="uuc-f2-OFX" firstAttribute="leading" secondItem="TKI-a9-bRX" secondAttribute="leading" id="PQj-is-Zlx"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="QCu-J4-0yV" secondAttribute="trailing" id="QVh-z8-aNJ"/>
|
||||
<constraint firstItem="UHg-1l-FlD" firstAttribute="leading" secondItem="CeE-AE-hRG" secondAttribute="leading" id="QlP-bI-uga"/>
|
||||
<constraint firstItem="EH5-aS-E55" firstAttribute="top" secondItem="uJD-OF-YVY" secondAttribute="top" id="VDU-as-fdx"/>
|
||||
<constraint firstAttribute="bottom" secondItem="uuc-f2-OFX" secondAttribute="bottom" constant="1" id="YA7-Xm-cFO"/>
|
||||
<constraint firstItem="Q6M-Iz-Ypx" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="uJD-OF-YVY" secondAttribute="leading" id="Ygv-ha-RLn"/>
|
||||
<constraint firstItem="uuc-f2-OFX" firstAttribute="top" secondItem="UHg-1l-FlD" secondAttribute="bottom" constant="20" symbolic="YES" id="aqe-xY-QDd"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="TKI-a9-bRX" secondAttribute="trailing" id="bLP-TU-TeL"/>
|
||||
<constraint firstItem="OSS-A0-uUS" firstAttribute="centerX" secondItem="uJD-OF-YVY" secondAttribute="centerX" id="bsc-Gf-dIS"/>
|
||||
<constraint firstItem="SUN-k3-ZEb" firstAttribute="trailing" secondItem="Q6M-Iz-Ypx" secondAttribute="trailing" id="c23-mt-Mfd"/>
|
||||
<constraint firstItem="SUN-k3-ZEb" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="uJD-OF-YVY" secondAttribute="leading" id="dj1-Uj-ibG"/>
|
||||
<constraint firstItem="MzL-QQ-2oL" firstAttribute="top" secondItem="CeE-AE-hRG" secondAttribute="bottom" constant="8" symbolic="YES" id="emd-u5-hgZ"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="uuc-f2-OFX" secondAttribute="trailing" id="gBp-7A-zG6"/>
|
||||
<constraint firstItem="CeE-AE-hRG" firstAttribute="top" secondItem="QCu-J4-0yV" secondAttribute="bottom" constant="6" symbolic="YES" id="hYd-1l-oMg"/>
|
||||
<constraint firstItem="T4A-0o-p2w" firstAttribute="firstBaseline" secondItem="EH5-aS-E55" secondAttribute="firstBaseline" id="jvM-Qd-rbB"/>
|
||||
<constraint firstItem="TKI-a9-bRX" firstAttribute="top" secondItem="MzL-QQ-2oL" secondAttribute="bottom" constant="8" symbolic="YES" id="lIt-NB-IL8"/>
|
||||
@@ -475,7 +508,6 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="privacyPolicyTextField" destination="OSS-A0-uUS" id="tmS-6L-sxr"/>
|
||||
<outlet property="releaseBuildsButton" destination="QCu-J4-0yV" id="mjo-l6-P3b"/>
|
||||
<outlet property="testBuildsButton" destination="CeE-AE-hRG" id="mFo-DS-g83"/>
|
||||
</connections>
|
||||
@@ -495,16 +527,16 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="7UM-iq-OLB" customClass="PreferencesTableViewBackgroundView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="44" width="180" height="213"/>
|
||||
<rect key="frame" x="20" y="44" width="180" height="219"/>
|
||||
<subviews>
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PaF-du-r3c">
|
||||
<rect key="frame" x="1" y="0.0" width="178" height="212"/>
|
||||
<rect key="frame" x="1" y="1" width="178" height="217"/>
|
||||
<clipView key="contentView" id="cil-Gq-akO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="178" height="212"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="178" height="217"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" tableStyle="fullWidth" columnReordering="NO" columnSelection="YES" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" viewBased="YES" id="aTp-KR-y6b">
|
||||
<rect key="frame" x="0.0" y="0.0" width="178" height="212"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="178" height="217"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -579,7 +611,7 @@
|
||||
<constraint firstItem="PaF-du-r3c" firstAttribute="leading" secondItem="7UM-iq-OLB" secondAttribute="leading" constant="1" id="Brq-cg-FVo"/>
|
||||
<constraint firstItem="PaF-du-r3c" firstAttribute="top" secondItem="7UM-iq-OLB" secondAttribute="top" constant="1" id="G3u-Hk-xlH"/>
|
||||
<constraint firstAttribute="width" constant="180" id="MWF-uR-jbC"/>
|
||||
<constraint firstAttribute="bottom" secondItem="PaF-du-r3c" secondAttribute="bottom" id="bjN-h8-jtK"/>
|
||||
<constraint firstAttribute="bottom" secondItem="PaF-du-r3c" secondAttribute="bottom" constant="1" id="bjN-h8-jtK"/>
|
||||
<constraint firstAttribute="trailing" secondItem="PaF-du-r3c" secondAttribute="trailing" constant="1" id="dfm-a5-dYc"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
@@ -611,7 +643,7 @@
|
||||
<rect key="frame" x="83" y="20" width="117" height="24"/>
|
||||
</customView>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="Y7D-xQ-wep">
|
||||
<rect key="frame" x="208" y="20" width="222" height="237"/>
|
||||
<rect key="frame" x="208" y="20" width="222" height="243"/>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
@@ -642,7 +674,7 @@
|
||||
</viewController>
|
||||
<customObject id="AgZ-2t-A2h" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-558" y="830"/>
|
||||
<point key="canvasLocation" x="-558" y="935"/>
|
||||
</scene>
|
||||
<!--Container-->
|
||||
<scene sceneID="fzS-hg-3TF">
|
||||
@@ -666,16 +698,16 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="pjs-G4-byk" customClass="PreferencesTableViewBackgroundView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="44" width="180" height="213"/>
|
||||
<rect key="frame" x="20" y="44" width="180" height="219"/>
|
||||
<subviews>
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="29T-r2-ckC">
|
||||
<rect key="frame" x="1" y="0.0" width="178" height="212"/>
|
||||
<rect key="frame" x="1" y="1" width="178" height="217"/>
|
||||
<clipView key="contentView" id="dXw-GY-TP8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="178" height="212"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="178" height="217"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" tableStyle="fullWidth" columnReordering="NO" columnSelection="YES" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" viewBased="YES" id="dfn-Vn-oDp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="178" height="212"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="178" height="217"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -744,7 +776,7 @@
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="180" id="0gU-oR-pQf"/>
|
||||
<constraint firstAttribute="bottom" secondItem="29T-r2-ckC" secondAttribute="bottom" id="BMY-9E-vH2"/>
|
||||
<constraint firstAttribute="bottom" secondItem="29T-r2-ckC" secondAttribute="bottom" constant="1" id="BMY-9E-vH2"/>
|
||||
<constraint firstAttribute="trailing" secondItem="29T-r2-ckC" secondAttribute="trailing" constant="1" id="dAW-1i-3iD"/>
|
||||
<constraint firstItem="29T-r2-ckC" firstAttribute="top" secondItem="pjs-G4-byk" secondAttribute="top" constant="1" id="tAi-6L-Tjj"/>
|
||||
<constraint firstItem="29T-r2-ckC" firstAttribute="leading" secondItem="pjs-G4-byk" secondAttribute="leading" constant="1" id="wXE-ze-ubv"/>
|
||||
@@ -778,7 +810,7 @@
|
||||
<rect key="frame" x="83" y="20" width="117" height="24"/>
|
||||
</customView>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="N1N-pE-gBL">
|
||||
<rect key="frame" x="208" y="20" width="222" height="237"/>
|
||||
<rect key="frame" x="208" y="20" width="222" height="243"/>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
@@ -809,7 +841,7 @@
|
||||
</viewController>
|
||||
<customObject id="Cne-wm-w1Q" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-36" y="826"/>
|
||||
<point key="canvasLocation" x="-36" y="931"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
|
||||
@@ -27,7 +27,7 @@ class UnreadCountView : NSView {
|
||||
}
|
||||
}
|
||||
var unreadCountString: String {
|
||||
return unreadCount < 1 ? "" : "\(unreadCount)"
|
||||
return unreadCount < 1 ? "" : numberFormatter.string(from: NSNumber(value: unreadCount))!
|
||||
}
|
||||
|
||||
private var intrinsicContentSizeIsValid = false
|
||||
@@ -92,5 +92,21 @@ class UnreadCountView : NSView {
|
||||
unreadCountString.draw(at: textRect().origin, withAttributes: Appearance.textAttributes)
|
||||
}
|
||||
}
|
||||
|
||||
var numberFormatter: NumberFormatter!
|
||||
|
||||
override init(frame frameRect: NSRect) {
|
||||
super.init(frame: frameRect)
|
||||
self.frame = frameRect
|
||||
|
||||
let formatter = NumberFormatter()
|
||||
formatter.locale = Locale.current
|
||||
formatter.numberStyle = .decimal
|
||||
numberFormatter = formatter
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ private extension TimelineViewController {
|
||||
func menu(for articles: [Article]) -> NSMenu? {
|
||||
let menu = NSMenu(title: "")
|
||||
|
||||
if articles.anyArticleIsUnread() {
|
||||
if articles.anyArticleIsUnreadAndCanMarkRead() {
|
||||
menu.addItem(markReadMenuItem(articles))
|
||||
}
|
||||
if articles.anyArticleIsReadAndCanMarkUnread() {
|
||||
@@ -169,10 +169,10 @@ private extension TimelineViewController {
|
||||
if articles.anyArticleIsStarred() {
|
||||
menu.addItem(markUnstarredMenuItem(articles))
|
||||
}
|
||||
if let first = articles.first, self.articles.articlesAbove(article: first).canMarkAllAsRead() {
|
||||
if let first = articles.first, self.articles.articlesAbove(article: first).canMarkAllAsRead(exemptArticles: directlyMarkedAsUnreadArticles) {
|
||||
menu.addItem(markAboveReadMenuItem(articles))
|
||||
}
|
||||
if let last = articles.last, self.articles.articlesBelow(article: last).canMarkAllAsRead() {
|
||||
if let last = articles.last, self.articles.articlesBelow(article: last).canMarkAllAsRead(exemptArticles: directlyMarkedAsUnreadArticles) {
|
||||
menu.addItem(markBelowReadMenuItem(articles))
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
}
|
||||
|
||||
directlyMarkedAsUnreadArticles = Set<Article>()
|
||||
lastVerticalPosition = 0
|
||||
articleRowMap = [String: [Int]]()
|
||||
tableView.reloadData()
|
||||
}
|
||||
@@ -194,6 +195,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
private let keyboardDelegate = TimelineKeyboardDelegate()
|
||||
private var timelineShowsSeparatorsObserver: NSKeyValueObservation?
|
||||
|
||||
private var markAsReadOnScrollWorkItem: DispatchWorkItem?
|
||||
private var markAsReadOnScrollStart: Int?
|
||||
private var markAsReadOnScrollEnd: Int?
|
||||
private var lastVerticalPosition: CGFloat = 0
|
||||
|
||||
convenience init(delegate: TimelineDelegate) {
|
||||
self.init(nibName: "TimelineTableView", bundle: nil)
|
||||
self.delegate = delegate
|
||||
@@ -224,6 +230,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(markStatusCommandDidDirectMarking(_:)), name: .MarkStatusCommandDidDirectMarking, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(markStatusCommandDidUndoDirectMarking(_:)), name: .MarkStatusCommandDidUndoDirectMarking, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(scrollViewDidScroll), name: NSScrollView.didLiveScrollNotification, object: tableView.enclosingScrollView)
|
||||
didRegisterForNotifications = true
|
||||
}
|
||||
}
|
||||
@@ -235,6 +242,10 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
// MARK: - API
|
||||
|
||||
func markAllAsRead(completion: (() -> Void)? = nil) {
|
||||
markAllAsRead(articles, completion: completion)
|
||||
}
|
||||
|
||||
func markAllAsRead(_ articles: [Article], completion: (() -> Void)? = nil) {
|
||||
let markableArticles = Set(articles).subtracting(directlyMarkedAsUnreadArticles)
|
||||
guard let undoManager = undoManager,
|
||||
let markReadCommand = MarkStatusCommand(initialArticles: markableArticles,
|
||||
@@ -248,7 +259,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
}
|
||||
|
||||
func canMarkAllAsRead() -> Bool {
|
||||
return articles.canMarkAllAsRead()
|
||||
return articles.canMarkAllAsRead(exemptArticles: directlyMarkedAsUnreadArticles)
|
||||
}
|
||||
|
||||
func canMarkSelectedArticlesAsRead() -> Bool {
|
||||
@@ -329,6 +340,10 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
let urlStrings = selectedArticles.compactMap { $0.preferredLink }
|
||||
Browser.open(urlStrings, fromWindow: self.view.window, invertPreference: NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false)
|
||||
}
|
||||
|
||||
@objc func scrollViewDidScroll(notification: Notification) {
|
||||
markAsReadOnScroll()
|
||||
}
|
||||
|
||||
@IBAction func toggleStatusOfSelectedArticles(_ sender: Any?) {
|
||||
guard !selectedArticles.isEmpty else {
|
||||
@@ -474,7 +489,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
func markReadCommandStatus() -> MarkCommandValidationStatus {
|
||||
let articles = selectedArticles
|
||||
|
||||
if articles.anyArticleIsUnread() {
|
||||
if articles.anyArticleIsUnreadAndCanMarkRead() {
|
||||
return .canMark
|
||||
}
|
||||
|
||||
@@ -499,12 +514,12 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
|
||||
func canMarkAboveArticlesAsRead() -> Bool {
|
||||
guard let first = selectedArticles.first else { return false }
|
||||
return articles.articlesAbove(article: first).canMarkAllAsRead()
|
||||
return articles.articlesAbove(article: first).canMarkAllAsRead(exemptArticles: directlyMarkedAsUnreadArticles)
|
||||
}
|
||||
|
||||
func canMarkBelowArticlesAsRead() -> Bool {
|
||||
guard let last = selectedArticles.last else { return false }
|
||||
return articles.articlesBelow(article: last).canMarkAllAsRead()
|
||||
return articles.articlesBelow(article: last).canMarkAllAsRead(exemptArticles: directlyMarkedAsUnreadArticles)
|
||||
}
|
||||
|
||||
func markOlderArticlesRead(_ selectedArticles: [Article]) {
|
||||
@@ -1326,4 +1341,51 @@ private extension TimelineViewController {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func markAsReadOnScroll() {
|
||||
guard AppDefaults.shared.markArticlesAsReadOnScroll else { return }
|
||||
|
||||
// Only try to mark if we are scrolling up
|
||||
defer {
|
||||
lastVerticalPosition = tableView.enclosingScrollView?.documentVisibleRect.origin.y ?? 0
|
||||
}
|
||||
guard lastVerticalPosition < tableView.enclosingScrollView?.documentVisibleRect.origin.y ?? 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure we are a little past the visible area so that marking isn't too touchy
|
||||
let firstVisibleRowIndex = tableView.rows(in: tableView.visibleRect).location
|
||||
guard let firstVisibleRowRect = tableView.rowView(atRow: firstVisibleRowIndex, makeIfNecessary: false)?.frame,
|
||||
tableView.convert(firstVisibleRowRect, to: tableView.enclosingScrollView).origin.y < tableView.safeAreaInsets.top - 20 else {
|
||||
return
|
||||
}
|
||||
|
||||
// We only mark immediately after scrolling stops, not during, to prevent scroll hitching
|
||||
markAsReadOnScrollWorkItem?.cancel()
|
||||
markAsReadOnScrollWorkItem = DispatchWorkItem { [weak self] in
|
||||
defer {
|
||||
self?.markAsReadOnScrollStart = nil
|
||||
self?.markAsReadOnScrollEnd = nil
|
||||
}
|
||||
|
||||
guard let start: Int = self?.markAsReadOnScrollStart,
|
||||
let end: Int = self?.markAsReadOnScrollEnd ?? self?.markAsReadOnScrollStart,
|
||||
start <= end,
|
||||
let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let articles = self.articles[start...end].filter({ $0.status.read == false })
|
||||
self.markAllAsRead(articles)
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: markAsReadOnScrollWorkItem!)
|
||||
|
||||
// Here we are creating a range of rows to attempt to mark later with the work item
|
||||
guard markAsReadOnScrollStart != nil else {
|
||||
markAsReadOnScrollStart = max(firstVisibleRowIndex - 5, 0)
|
||||
return
|
||||
}
|
||||
markAsReadOnScrollEnd = max(markAsReadOnScrollEnd ?? 0, firstVisibleRowIndex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,24 +36,26 @@ a:hover {
|
||||
|
||||
:root {
|
||||
--header-table-border-color: rgba(0, 0, 0, 0.1);
|
||||
--header-color: rgba(0, 0, 0, 0.3);
|
||||
--body-code-color: #666;
|
||||
--header-color: rgba(0, 0, 0, 0.66);
|
||||
--body-code-color: #111;
|
||||
--code-background-color: #eee;
|
||||
--system-message-color: #cbcbcb;
|
||||
--feedlink-color: rgba(255, 0, 0, 0.6);
|
||||
--article-title-color: #333;
|
||||
--article-date-color: rgba(0, 0, 0, 0.3);
|
||||
--article-date-color: rgba(0, 0, 0, 0.5);
|
||||
--table-cell-border-color: lightgray;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--header-color: rgba(94, 158, 244, 1);
|
||||
--body-code-color: #b2b2b2;
|
||||
--body-code-color: #dcdcdc;
|
||||
--system-message-color: #5f5f5f;
|
||||
--feedlink-color: rgba(94, 158, 244, 1);
|
||||
--article-title-color: #e0e0e0;
|
||||
--article-date-color: rgba(255, 255, 255, 0.5);
|
||||
--table-cell-border-color: dimgray;
|
||||
--code-background-color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +108,8 @@ body > .systemMessage {
|
||||
.articleDateline {
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
font-variant-caps: all-small-caps;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
.articleDateline a:link, .articleDateline a:visited {
|
||||
@@ -115,6 +119,7 @@ body > .systemMessage {
|
||||
.articleDatelineTitle {
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
font-variant-caps: all-small-caps;
|
||||
}
|
||||
|
||||
.articleDatelineTitle a:link, .articleDatelineTitle a:visited {
|
||||
@@ -122,19 +127,37 @@ body > .systemMessage {
|
||||
}
|
||||
|
||||
.externalLink {
|
||||
margin-bottom: 5px;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
/*
|
||||
font-variant-caps: all-small-caps;
|
||||
letter-spacing: 0.025em;
|
||||
*/
|
||||
font-size: 0.875em;
|
||||
font-style: italic;
|
||||
color: var(--article-date-color);
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.externalLink a {
|
||||
font-family: "SF Mono", Menlo, Courier, monospace;
|
||||
font-size: 0.85em;
|
||||
font-variant-caps: normal;
|
||||
letter-spacing: 0em;
|
||||
}
|
||||
|
||||
|
||||
.articleBody {
|
||||
margin-top: 20px;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
.articleBody a {
|
||||
padding: 0px 1px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
line-height: 1.15em;
|
||||
font-weight: bold;
|
||||
@@ -149,6 +172,7 @@ pre {
|
||||
overflow-y: hidden;
|
||||
word-wrap: normal;
|
||||
word-break: normal;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
pre {
|
||||
@@ -156,9 +180,15 @@ pre {
|
||||
}
|
||||
|
||||
code, pre {
|
||||
font-family: "SF Mono", Menlo, "Courier New", Courier, monospace;
|
||||
font-family: "SF Mono", Menlo, Courier, monospace;
|
||||
font-size: 1em;
|
||||
-webkit-hyphens: none;
|
||||
background: var(--code-background-color);
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 1px 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
pre code {
|
||||
@@ -219,10 +249,6 @@ img, figure, video, div, object {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
iframe {
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
@@ -238,7 +264,6 @@ figure {
|
||||
}
|
||||
|
||||
figcaption {
|
||||
margin-top: 0.5em;
|
||||
font-size: 14px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
@@ -286,6 +311,30 @@ blockquote {
|
||||
border-top: 1px solid var(--header-table-border-color);
|
||||
}
|
||||
|
||||
/* Twitter */
|
||||
|
||||
.twitterAvatar {
|
||||
vertical-align: middle;
|
||||
border-radius: 4px;
|
||||
height: 1.7em;
|
||||
width: 1.7em;
|
||||
}
|
||||
|
||||
.twitterUsername {
|
||||
line-height: 1.2;
|
||||
margin-left: 4px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.twitterScreenName {
|
||||
font-size: 66%;
|
||||
}
|
||||
|
||||
.twitterTimestamp {
|
||||
font-size: 66%;
|
||||
}
|
||||
|
||||
/* Newsfoot theme for light mode (default) */
|
||||
.newsfoot-footnote-popover {
|
||||
background: #ccc;
|
||||
@@ -359,7 +408,6 @@ a.footnote:hover,
|
||||
padding-right: 20px;
|
||||
|
||||
word-break: break-word;
|
||||
-webkit-hyphens: auto;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
@@ -370,7 +418,8 @@ a.footnote:hover,
|
||||
font-size: [[font-size]]px;
|
||||
--primary-accent-color: #086AEE;
|
||||
--secondary-accent-color: #086AEE;
|
||||
--block-quote-border-color: rgba(8, 106, 238, 0.75);
|
||||
--block-quote-border-color: rgba(0, 0, 0, 0.25);
|
||||
--ios-hover-color: lightgray; /* placeholder */
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: dark) {
|
||||
@@ -379,24 +428,44 @@ a.footnote:hover,
|
||||
--secondary-accent-color: #5E9EF4;
|
||||
--block-quote-border-color: rgba(94, 158, 244, 0.75);
|
||||
--header-table-border-color: rgba(255, 255, 255, 0.2);
|
||||
--ios-hover-color: #444444; /* placeholder */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
body a, body a:visited, body a * {
|
||||
color: var(--secondary-accent-color);
|
||||
}
|
||||
|
||||
.externalLink a {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.articleBody a:link, .articleBody a:visited {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid var(--primary-accent-color);
|
||||
color: var(--secondary-accent-color);
|
||||
}
|
||||
|
||||
body .header {
|
||||
font: -apple-system-body;
|
||||
font-size: [[font-size]]px;
|
||||
}
|
||||
|
||||
body .header a:link, body .header a:visited {
|
||||
color: var(--primary-accent-color);
|
||||
color: var(--secondary-accent-color);
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: coarse) {
|
||||
.articleBody a:hover {
|
||||
background: var(--ios-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pre {
|
||||
/*
|
||||
border: 1px solid var(--secondary-accent-color);
|
||||
*/
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@@ -439,15 +508,19 @@ a.footnote:hover,
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--accent-color: rgba(8, 106, 238, 1);
|
||||
--block-quote-border-color: rgba(8, 106, 238, .50);
|
||||
--accent-color: rgba( 8, 106, 238, 1);
|
||||
--block-quote-border-color: rgba( 0, 0, 0, 0.25);
|
||||
--hover-gradient-color-start: rgba(60, 146, 251, 1);
|
||||
--hover-gradient-color-end: rgba(67, 149, 251, 1);
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--accent-color: rgba(94, 158, 244, 1);
|
||||
--block-quote-border-color: rgba(94, 158, 244, .50);
|
||||
--header-table-border-color: rgba(255, 255, 255, 0.1);
|
||||
--accent-color: rgba( 94, 158, 244, 1);
|
||||
--block-quote-border-color: rgba( 94, 158, 244, 0.50);
|
||||
--header-table-border-color: rgba(255, 255, 255, 0.1);
|
||||
--hover-gradient-color-start: rgba( 41, 121, 213, 1);
|
||||
--hover-gradient-color-end: rgba( 42, 120, 212, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,8 +528,26 @@ a.footnote:hover,
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.articleBody a:link: not(a > img, a > code), .articleBody a:visited: not(a > img, a > code) {
|
||||
/* text-decoration: underline; */
|
||||
border-bottom: 1px solid var(--accent-color);
|
||||
}
|
||||
.articleBody a:hover {
|
||||
border-radius: 2px;
|
||||
/*
|
||||
background: var(--accent-color);
|
||||
*/
|
||||
background: linear-gradient(0deg, var(--hover-gradient-color-start) 0%, var(--hover-gradient-color-end) 100%);
|
||||
border-bottom: 1px solid var(--hover-gradient-color-end);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
pre {
|
||||
/*
|
||||
border: 1px solid var(--accent-color);
|
||||
*/
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ final class MarkStatusCommand: UndoableCommand {
|
||||
}
|
||||
|
||||
convenience init?(initialArticles: [Article], statusKey: ArticleStatus.Key, flag: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
|
||||
self.init(initialArticles: Set(initialArticles), statusKey: .read, flag: flag, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion)
|
||||
self.init(initialArticles: Set(initialArticles), statusKey: statusKey, flag: flag, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion)
|
||||
}
|
||||
|
||||
convenience init?(initialArticles: Set<Article>, markingRead: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
|
||||
|
||||
@@ -4,25 +4,6 @@
|
||||
<title>Default Feeds</title>
|
||||
</head>
|
||||
<body>
|
||||
<<<<<<< HEAD
|
||||
<outline text="Colossal" title="Colossal" type="rss" version="RSS" htmlUrl="https://www.thisiscolossal.com/" xmlUrl="https://www.thisiscolossal.com/feed/"/>
|
||||
<outline text="Becky Hansmeyer" title="Becky Hansmeyer" type="rss" version="RSS" htmlUrl="https://beckyhansmeyer.com" xmlUrl="https://beckyhansmeyer.com/feed/"/>
|
||||
<outline text="Maurice Parker" title="Maurice Parker" type="rss" version="RSS" htmlUrl="https://vincode.io/" xmlUrl="https://vincode.io/feed.xml"/>
|
||||
<outline text="Maurice Parker" title="Maurice Parker" type="rss" version="RSS" htmlUrl="https://vincode.io/" xmlUrl="https://vincode.io/feed.xml"/>
|
||||
<outline text="Daring Fireball" title="Daring Fireball" type="rss" version="RSS" htmlUrl="https://daringfireball.net/" xmlUrl="https://daringfireball.net/feeds/json"/>
|
||||
<outline text="Manton Reece" title="Manton Reece" type="rss" version="RSS" htmlUrl="https://manton.org/" xmlUrl="https://www.manton.org/feed/json"/>
|
||||
<outline text="inessential" title="inessential" type="rss" version="RSS" htmlUrl="https://inessential.com/" xmlUrl="https://inessential.com/feed.json"/>
|
||||
<outline text="Julia Evans" title="Julia Evans" type="rss" version="RSS" htmlUrl="https://jvns.ca/" xmlUrl="https://jvns.ca/atom.xml"/>
|
||||
<outline text="Jason Kottke" title="Jason Kottke" type="rss" version="RSS" htmlUrl="https://kottke.org/" xmlUrl="http://feeds.kottke.org/json"/>
|
||||
<outline text="Six Colors" title="Six Colors" type="rss" version="RSS" htmlUrl="https://sixcolors.com/" xmlUrl="https://feedpress.me/sixcolors?type=xml"/>
|
||||
<outline text="inessential" title="inessential" type="rss" version="RSS" htmlUrl="https://inessential.com/" xmlUrl="https://inessential.com/feed.json"/>
|
||||
<outline text="NetNewsWire Blog" title="NetNewsWire Blog" type="rss" version="RSS" htmlUrl="https://nnw.ranchero.com/" xmlUrl="https://nnw.ranchero.com/feed.json"/>
|
||||
<outline text="Erica Sadun" title="Erica Sadun" type="rss" version="RSS" htmlUrl="https://ericasadun.com/" xmlUrl="https://ericasadun.com/feed/"/>
|
||||
<outline text="One Foot Tsunami" title="One Foot Tsunami" type="rss" version="RSS" htmlUrl="https://onefoottsunami.com/" xmlUrl="https://onefoottsunami.com/feed/json/"/>
|
||||
<outline text="Craig Hockenberry" title="Craig Hockenberry" type="rss" version="RSS" htmlUrl="https://furbo.org/" xmlUrl="https://furbo.org/feed/json"/>
|
||||
<outline text="Rose Orchard" title="Rose Orchard" type="rss" version="RSS" htmlUrl="https://rosemaryorchard.com/" xmlUrl="https://rosemaryorchard.com/feed.xml"/>
|
||||
<outline text="Michael Tsai" title="Michael Tsai" type="rss" version="RSS" htmlUrl="https://mjtsai.com/blog/" xmlUrl="https://mjtsai.com/blog/feed/"/>
|
||||
=======
|
||||
<outline text="BBC News - World" title="BBC News - World" type="rss" version="RSS" htmlUrl="https://www.bbc.com/news" xmlUrl="https://feeds.bbci.co.uk/news/world/rss.xml"/>
|
||||
<outline text="Becky Hansmeyer" title="Becky Hansmeyer" type="rss" version="RSS" htmlUrl="https://beckyhansmeyer.com" xmlUrl="https://beckyhansmeyer.com/feed/"/>
|
||||
<outline text="Colossal" title="Colossal" type="rss" version="RSS" htmlUrl="https://www.thisiscolossal.com/" xmlUrl="https://www.thisiscolossal.com/feed/"/>
|
||||
@@ -33,6 +14,5 @@
|
||||
<outline text="NetNewsWire Blog" title="NetNewsWire Blog" type="rss" version="RSS" htmlUrl="https://nnw.ranchero.com/" xmlUrl="https://nnw.ranchero.com/feed.json"/>
|
||||
<outline text="One Foot Tsunami" title="One Foot Tsunami" type="rss" version="RSS" htmlUrl="https://onefoottsunami.com/" xmlUrl="https://onefoottsunami.com/feed/json/"/>
|
||||
<outline text="Six Colors" title="Six Colors" type="rss" version="RSS" htmlUrl="https://sixcolors.com/" xmlUrl="https://feedpress.me/sixcolors?type=xml"/>
|
||||
>>>>>>> ios-release
|
||||
</body>
|
||||
</opml>
|
||||
|
||||
@@ -2,15 +2,7 @@
|
||||
%{
|
||||
import os
|
||||
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
secrets = ['MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'TWITTER_CONSUMER_KEY', 'TWITTER_CONSUMER_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
|
||||
=======
|
||||
secrets = ['FEED_WRANGLER_KEY', 'MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
|
||||
>>>>>>> mac-release
|
||||
=======
|
||||
secrets = ['FEED_WRANGLER_KEY', 'MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
|
||||
>>>>>>> ios-release
|
||||
secrets = ['MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
|
||||
|
||||
def chunks(seq, size):
|
||||
return (seq[i:(i + size)] for i in range(0, len(seq), size))
|
||||
|
||||
@@ -54,8 +54,8 @@ extension Array where Element == Article {
|
||||
return ArticleSorter.sortedByDate(articles: self, sortDirection: sortDirection, groupByFeed: groupByFeed)
|
||||
}
|
||||
|
||||
func canMarkAllAsRead() -> Bool {
|
||||
return anyArticleIsUnread()
|
||||
func canMarkAllAsRead(exemptArticles: Set<Article> = .init()) -> Bool {
|
||||
return anyArticleIsUnreadAndCanMarkRead(exemptArticles: exemptArticles)
|
||||
}
|
||||
|
||||
func anyArticlePassesTest(_ test: ((Article) -> Bool)) -> Bool {
|
||||
@@ -71,8 +71,8 @@ extension Array where Element == Article {
|
||||
return anyArticlePassesTest { $0.status.read && $0.isAvailableToMarkUnread }
|
||||
}
|
||||
|
||||
func anyArticleIsUnread() -> Bool {
|
||||
return anyArticlePassesTest { !$0.status.read }
|
||||
func anyArticleIsUnreadAndCanMarkRead(exemptArticles: Set<Article> = .init()) -> Bool {
|
||||
return anyArticlePassesTest { !(exemptArticles.contains($0) || $0.status.read) }
|
||||
}
|
||||
|
||||
func anyArticleIsStarred() -> Bool {
|
||||
|
||||
@@ -61,6 +61,7 @@ final class AppDefaults: ObservableObject {
|
||||
static let useSystemBrowser = "useSystemBrowser"
|
||||
static let currentThemeName = "currentThemeName"
|
||||
static let twitterDeprecationAlertShown = "twitterDeprecationAlertShown"
|
||||
static let markArticlesAsReadOnScroll = "markArticlesAsReadOnScroll"
|
||||
}
|
||||
|
||||
let isDeveloperBuild: Bool = {
|
||||
@@ -266,6 +267,15 @@ final class AppDefaults: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
var markArticlesAsReadOnScroll: Bool {
|
||||
get {
|
||||
return AppDefaults.bool(for: Key.markArticlesAsReadOnScroll)
|
||||
}
|
||||
set {
|
||||
AppDefaults.setBool(for: Key.markArticlesAsReadOnScroll, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
static func registerDefaults() {
|
||||
let defaults: [String : Any] = [Key.userInterfaceColorPalette: UserInterfaceColorPalette.automatic.rawValue,
|
||||
Key.timelineGroupByFeed: false,
|
||||
|
||||
@@ -35,8 +35,10 @@ class MasterFeedUnreadCountView : UIView {
|
||||
}
|
||||
|
||||
var unreadCountString: String {
|
||||
return unreadCount < 1 ? "" : "\(unreadCount)"
|
||||
return unreadCount < 1 ? "" : numberFormatter.string(from: NSNumber(value: unreadCount))!
|
||||
}
|
||||
|
||||
var numberFormatter: NumberFormatter!
|
||||
|
||||
private var contentSizeIsValid = false
|
||||
private var _contentSize = CGSize.zero
|
||||
@@ -44,11 +46,21 @@ class MasterFeedUnreadCountView : UIView {
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.isOpaque = false
|
||||
|
||||
let formatter = NumberFormatter()
|
||||
formatter.locale = Locale.current
|
||||
formatter.numberStyle = .decimal
|
||||
numberFormatter = formatter
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.isOpaque = false
|
||||
|
||||
let formatter = NumberFormatter()
|
||||
formatter.locale = Locale.current
|
||||
formatter.numberStyle = .decimal
|
||||
numberFormatter = formatter
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
|
||||
@@ -564,6 +564,11 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
|
||||
return
|
||||
}
|
||||
|
||||
if tableView.window == nil {
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
|
||||
tableView.performBatchUpdates {
|
||||
if let deletes = changes.deletes, !deletes.isEmpty {
|
||||
tableView.deleteSections(IndexSet(deletes), with: .middle)
|
||||
|
||||
@@ -31,11 +31,15 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
private lazy var dataSource = makeDataSource()
|
||||
private let searchController = UISearchController(searchResultsController: nil)
|
||||
|
||||
private var markAsReadOnScrollWorkItem: DispatchWorkItem?
|
||||
private var markAsReadOnScrollStart: Int?
|
||||
private var markAsReadOnScrollEnd: Int?
|
||||
private var lastVerticlePosition: CGFloat = 0
|
||||
|
||||
var mainControllerIdentifier = MainControllerIdentifier.masterTimeline
|
||||
|
||||
weak var coordinator: SceneCoordinator!
|
||||
var undoableCommands = [UndoableCommand]()
|
||||
let scrollPositionQueue = CoalescingQueue(name: "Timeline Scroll Position", interval: 0.3, maxInterval: 1.0)
|
||||
|
||||
private let keyboardManager = KeyboardManager(type: .timeline)
|
||||
override var keyCommands: [UIKeyCommand]? {
|
||||
@@ -434,7 +438,8 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
}
|
||||
|
||||
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
scrollPositionQueue.add(self, #selector(scrollPositionDidChange))
|
||||
coordinator.timelineMiddleIndexPath = tableView.middleVisibleRow()
|
||||
markAsReadOnScroll()
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
@@ -530,10 +535,6 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
updateUI()
|
||||
}
|
||||
|
||||
@objc func scrollPositionDidChange() {
|
||||
coordinator.timelineMiddleIndexPath = tableView.middleVisibleRow()
|
||||
}
|
||||
|
||||
// MARK: Reloading
|
||||
|
||||
func queueReloadAvailableCells() {
|
||||
@@ -678,7 +679,7 @@ private extension MasterTimelineViewController {
|
||||
func updateToolbar() {
|
||||
guard firstUnreadButton != nil else { return }
|
||||
|
||||
markAllAsReadButton.isEnabled = coordinator.isTimelineUnreadAvailable
|
||||
markAllAsReadButton.isEnabled = coordinator.canMarkAllAsRead()
|
||||
firstUnreadButton.isEnabled = coordinator.isTimelineUnreadAvailable
|
||||
|
||||
if coordinator.isRootSplitCollapsed {
|
||||
@@ -695,6 +696,8 @@ private extension MasterTimelineViewController {
|
||||
}
|
||||
|
||||
func applyChanges(animated: Bool, completion: (() -> Void)? = nil) {
|
||||
lastVerticlePosition = 0
|
||||
|
||||
if coordinator.articles.count == 0 {
|
||||
tableView.rowHeight = tableView.estimatedRowHeight
|
||||
} else {
|
||||
@@ -723,7 +726,6 @@ private extension MasterTimelineViewController {
|
||||
}
|
||||
|
||||
func configure(_ cell: MasterTimelineTableViewCell, article: Article, indexPath: IndexPath) {
|
||||
|
||||
let iconImage = iconImageFor(article)
|
||||
let featuredImage = featuredImageFor(article)
|
||||
|
||||
@@ -748,6 +750,54 @@ private extension MasterTimelineViewController {
|
||||
return nil
|
||||
}
|
||||
|
||||
func markAsReadOnScroll() {
|
||||
// Only try to mark if we are scrolling up
|
||||
defer {
|
||||
lastVerticlePosition = tableView.contentOffset.y
|
||||
}
|
||||
guard lastVerticlePosition < tableView.contentOffset.y else {
|
||||
return
|
||||
}
|
||||
|
||||
// Implement Mark As Read on Scroll where we mark after the leading edge goes a little beyond the safe area inset
|
||||
guard AppDefaults.shared.markArticlesAsReadOnScroll,
|
||||
lastVerticlePosition < tableView.contentOffset.y,
|
||||
let firstVisibleIndexPath = tableView.indexPathsForVisibleRows?.first else { return }
|
||||
|
||||
let firstVisibleRowRect = tableView.rectForRow(at: firstVisibleIndexPath)
|
||||
guard tableView.convert(firstVisibleRowRect, to: nil).origin.y < tableView.safeAreaInsets.top - 20 else { return }
|
||||
|
||||
// We only mark immediately after scrolling stops, not during, to prevent scroll hitching
|
||||
markAsReadOnScrollWorkItem?.cancel()
|
||||
markAsReadOnScrollWorkItem = DispatchWorkItem { [weak self] in
|
||||
defer {
|
||||
self?.markAsReadOnScrollStart = nil
|
||||
self?.markAsReadOnScrollEnd = nil
|
||||
}
|
||||
|
||||
guard let start: Int = self?.markAsReadOnScrollStart,
|
||||
let end: Int = self?.markAsReadOnScrollEnd ?? self?.markAsReadOnScrollStart,
|
||||
start <= end,
|
||||
let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let articles = Array(start...end)
|
||||
.map({ IndexPath(row: $0, section: 0) })
|
||||
.compactMap({ self.dataSource.itemIdentifier(for: $0) })
|
||||
.filter({ $0.status.read == false })
|
||||
self.coordinator.markAllAsRead(articles)
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: markAsReadOnScrollWorkItem!)
|
||||
|
||||
// Here we are creating a range of rows to attempt to mark later with the work item
|
||||
guard markAsReadOnScrollStart != nil else {
|
||||
markAsReadOnScrollStart = max(firstVisibleIndexPath.row - 5, 0)
|
||||
return
|
||||
}
|
||||
markAsReadOnScrollEnd = max(markAsReadOnScrollEnd ?? 0, firstVisibleIndexPath.row)
|
||||
}
|
||||
|
||||
func toggleArticleReadStatusAction(_ article: Article) -> UIAction? {
|
||||
guard !article.status.read || article.isAvailableToMarkUnread else { return nil }
|
||||
|
||||
@@ -875,7 +925,7 @@ private extension MasterTimelineViewController {
|
||||
}
|
||||
|
||||
let articles = Array(fetchedArticles)
|
||||
guard articles.canMarkAllAsRead(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||
guard coordinator.canMarkAllAsRead(articles), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -898,7 +948,7 @@ private extension MasterTimelineViewController {
|
||||
}
|
||||
|
||||
let articles = Array(fetchedArticles)
|
||||
guard articles.canMarkAllAsRead(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||
guard coordinator.canMarkAllAsRead(articles), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 930 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 930 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-1024px 1.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-1024px.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
private var directlyMarkedAsUnreadArticles = Set<Article>()
|
||||
var directlyMarkedAsUnreadArticles = Set<Article>()
|
||||
|
||||
var prefersStatusBarHidden = false
|
||||
|
||||
@@ -1043,10 +1043,18 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
func canMarkAllAsRead() -> Bool {
|
||||
return articles.canMarkAllAsRead(exemptArticles: directlyMarkedAsUnreadArticles)
|
||||
}
|
||||
|
||||
func canMarkAllAsRead(_ articles: [Article]) -> Bool {
|
||||
return articles.canMarkAllAsRead(exemptArticles: directlyMarkedAsUnreadArticles)
|
||||
}
|
||||
|
||||
func canMarkAboveAsRead(for article: Article) -> Bool {
|
||||
let articlesAboveArray = articles.articlesAbove(article: article)
|
||||
return articlesAboveArray.canMarkAllAsRead()
|
||||
return articlesAboveArray.canMarkAllAsRead(exemptArticles: directlyMarkedAsUnreadArticles)
|
||||
}
|
||||
|
||||
func markAboveAsRead() {
|
||||
@@ -1064,7 +1072,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
|
||||
func canMarkBelowAsRead(for article: Article) -> Bool {
|
||||
let articleBelowArray = articles.articlesBelow(article: article)
|
||||
return articleBelowArray.canMarkAllAsRead()
|
||||
return articleBelowArray.canMarkAllAsRead(exemptArticles: directlyMarkedAsUnreadArticles)
|
||||
}
|
||||
|
||||
func markBelowAsRead() {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import RSCore
|
||||
|
||||
struct AboutView: View, LoadableAboutData {
|
||||
|
||||
@@ -35,9 +36,10 @@ struct AboutView: View, LoadableAboutData {
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack(alignment: .center, spacing: 8) {
|
||||
Image("About")
|
||||
Image(uiImage: RSImage.appIconImage!)
|
||||
.resizable()
|
||||
.frame(width: 75, height: 75)
|
||||
.cornerRadius(11)
|
||||
|
||||
Text(Bundle.main.appName)
|
||||
.font(.headline)
|
||||
|
||||
Reference in New Issue
Block a user