From f1aa7a4d7c93b8f6de012b8556bd2dfc843fb3a4 Mon Sep 17 00:00:00 2001 From: Adam Butler Date: Sun, 4 Apr 2021 22:25:26 +0200 Subject: [PATCH 1/4] first commit --- app/__mocks__/state.js | 6 +- app/components/AppStateListener.js | 178 +------------------------ app/containers/AppStateListener.js | 34 +---- app/docs/todo.md | 4 +- app/sagas/check-buckets.js | 201 +++++++++++++++++++++++++++++ app/sagas/fetch-items.js | 2 +- app/sagas/index.js | 3 + app/store/ui/types.ts | 1 + 8 files changed, 218 insertions(+), 211 deletions(-) create mode 100644 app/sagas/check-buckets.js diff --git a/app/__mocks__/state.js b/app/__mocks__/state.js index f4e5253f..76e68e9c 100644 --- a/app/__mocks__/state.js +++ b/app/__mocks__/state.js @@ -64,9 +64,9 @@ export const state = { "dropCapFamily": "header", "isCoverInline": true, }, - "showMercuryContent": true, + "showMercuryContent": false, "content_html": "It wasn’t all a mess. Here’s how the government and tech companies tamed foreign interference.", - "hasLoadedMercuryStuff": true, + "hasLoadedMercuryStuff": false, "date_published": 1615232395000, "original_created_at": 1615232395000, "id": "https://www.nytimes.com/2021/03/08/technology/what-went-right-in-the-2020-election.html", @@ -83,7 +83,7 @@ export const state = { "feed_id": "b63b3782-8a69-8a31-bd95-1b48674b71e3", "date_modified": 1615232395000, "feed_title": "NYT > Technology", - "showCoverImage": true, + "showCoverImage": false, "external_url": "https://www.nytimes.com/2021/03/08/technology/what-went-right-in-the-2020-election.html", "excerpt": "It wasn’t all a mess. Here’s how the government and tech companies tamed foreign interference.", "created_at": 1615232395000, diff --git a/app/components/AppStateListener.js b/app/components/AppStateListener.js index e740da2e..8cffabd5 100644 --- a/app/components/AppStateListener.js +++ b/app/components/AppStateListener.js @@ -12,31 +12,17 @@ import DarkModeListener from './DarkModeListener' class AppStateListener extends React.Component { - group = 'group.com.adam-butler.rizzle' - MINIMUM_UPDATE_INTERVAL = 600000 // 10 minutes constructor (props) { super(props) this.props = props - this.checkClipboard = this.checkClipboard.bind(this) - this.checkPageBucket = this.checkPageBucket.bind(this) - this.checkFeedBucket = this.checkFeedBucket.bind(this) this.handleAppStateChange = this.handleAppStateChange.bind(this) - this.showSavePageModal = this.showSavePageModal.bind(this) - this.showSaveFeedModal = this.showSaveFeedModal.bind(this) AppState.addEventListener('change', this.handleAppStateChange) - this.checkBuckets() - } - - async checkBuckets () { - await this.checkClipboard() - await this.checkPageBucket() - this.checkFeedBucket() - // see Rizzle component + this.props.checkBuckets() } async handleAppStateChange (nextAppState) { @@ -45,7 +31,7 @@ class AppStateListener extends React.Component { this.setState({ doNothing: Date.now() }) - await this.checkBuckets() + this.props.checkBuckets() if (!global.isStarting && (Date.now() - this.props.lastUpdated > this.MINIMUM_UPDATE_INTERVAL)) { this.props.fetchData() @@ -57,166 +43,6 @@ class AppStateListener extends React.Component { } } - async checkClipboard () { - console.log('Checking clipboard') - try { - let contents = await Clipboard.getString() - // TODO make this more robust - // right now we're ignoring any URLs that include 'rizzle.net' - // this is due to links getting into clipboard during the email auth process - if (contents.substring(0, 4) === 'http' && - contents.indexOf('rizzle.net') === -1) { - const isIgnored = await isIgnoredUrl(contents) - if (!isIgnored) { - this.showSavePageModal(contents, this, true) - } - } else if (contents.substring(0, 6) === '') { - } - } catch(err) { - log('checkClipboard', err) - } - } - - async checkPageBucket () { - SharedGroupPreferences.getItem('page', this.group).then(value => { - if (value !== null) { - SharedGroupPreferences.setItem('page', null, this.group) - value = typeof value === 'object' ? - value[0] : - value - console.log(`Got a page to save: ${value}`) - this.showSavePageModal(value, this) - } - }).catch(err => { - // '1' just means that there is nothing in the bucket - if (err !== 1) { - log('checkPageBucket', err) - } - }) - } - - checkFeedBucket () { - SharedGroupPreferences.getItem('feed', this.group).then(value => { - if (value !== null) { - const url = value - const that = this - SharedGroupPreferences.setItem('feed', null, this.group) - console.log(`Got a feed to subscribe to: ${url}`) - // TODO check that value is a feed url - // TODO check that feed is not already subscribed! - // right now it will just get ignored if it's already subscribed - // but it might be nice to say that in the message - fetch(url) - .then((response) => { - if (!response.ok) { - throw Error(response.statusText) - } - return response - }) - .then((response) => { - return response.text() - }) - .then((xml) => { - parseString(xml, (error, result) => { - if (error) { - throw error - } - let title, description - if (result.rss) { - title = typeof result.rss.channel[0].title[0] === 'string' ? - result.rss.channel[0].title[0] : - result.rss.channel[0].title[0]._ - description = result.rss.channel[0].description ? - (typeof result.rss.channel[0].description[0] === 'string' ? - result.rss.channel[0].description[0] : - result.rss.channel[0].description[0]._) : - '' - } else if (result.feed) { - // atom - title = typeof result.feed.title[0] === 'string' ? - result.feed.title[0] : - result.feed.title[0]._ - description = result.feed.subtitle ? - (typeof result.feed.subtitle[0] === 'string' ? - result.feed.subtitle[0] : - result.feed.subtitle[0]._) : - '' - } - this.showSaveFeedModal(url, title, description, that) - }) - }) - .catch(err => { - log('checkFeedBucket', err) - }) - } - }).catch(err => { - // '1' just means that there is nothing in the bucket - if (err !== 1) { - log('checkFeedBucket', err) - } - }) - } - - showSavePageModal (url, scope, isClipboard = false) { - let displayUrl = url - if (displayUrl.length > 64) { - displayUrl = displayUrl.slice(0, 64) + '…' - } - let modalText = [ - { - text: 'Save this page?', - style: ['title'] - }, - { - text: displayUrl, - style: ['em'] - } - ] - if (isClipboard) { - modalText.push({ - text: 'This URL was in your clipboard. Copying a URL is an easy way to save a page in Rizzle.', - style: ['hint'] - }) - } - this.props.showModal({ - modalText, - modalHideCancel: false, - modalShow: true, - modalOnOk: () => { - scope.props.saveURL(url) - } - }) - } - - showSaveFeedModal (url, title, description, scope) { - scope.props.showModal({ - modalText: [ - { - text: 'Add this feed?', - style: ['title'] - }, - { - text: title, - style: ['em'] - }, - { - text: description, - style: ['em', 'smaller'] - } - ], - modalHideCancel: false, - modalShow: true, - modalOnOk: () => { - scope.props.addFeed({ - url, - title, - description - }) - scope.props.fetchData() - } - }) - } - render () { return } diff --git a/app/containers/AppStateListener.js b/app/containers/AppStateListener.js index b7909905..307ec786 100644 --- a/app/containers/AppStateListener.js +++ b/app/containers/AppStateListener.js @@ -1,20 +1,12 @@ -import { - SAVE_EXTERNAL_URL, - ItemType -} from '../store/items/types' import { STATE_ACTIVE, STATE_INACTIVE } from '../store/config/types' import { connect } from 'react-redux' -import { ADD_FEED } from '../store/feeds/types' import { + CHECK_BUCKETS, FETCH_ITEMS, - SHOW_MODAL } from '../store/ui/types' -import { - SET_DISPLAY_MODE -} from '../store/items/types' import AppStateListener from '../components/AppStateListener' const mapStateToProps = (state) => { @@ -31,28 +23,10 @@ const mapDispatchToProps = (dispatch) => { fetchData: () => dispatch({ type: FETCH_ITEMS }), - updateCurrentAppState: (state) => dispatch(updateCurrentAppState(state)), - saveURL: (url) => { - dispatch({ - type: SAVE_EXTERNAL_URL, - url - }) - dispatch({ - type: SET_DISPLAY_MODE, - displayMode: ItemType.saved - }) - }, - addFeed: (feed) => dispatch({ - type: ADD_FEED, - feed + checkBuckets: () => dispatch({ + type: CHECK_BUCKETS }), - showModal: (modalProps) => { - console.log("SHOW MODAL!") - dispatch({ - type: SHOW_MODAL, - modalProps - }) - }, + updateCurrentAppState: (state) => dispatch(updateCurrentAppState(state)), appWentInactive: () => dispatch({ type: STATE_INACTIVE }), diff --git a/app/docs/todo.md b/app/docs/todo.md index ae11ddbb..abbe2d2e 100644 --- a/app/docs/todo.md +++ b/app/docs/todo.md @@ -1,7 +1,8 @@ # To Do -- the thing with headlines disappearing +- check bucket when closing browser - the thing with styles getting recalculated + - now suddenly I can't recreate it properly? - what's up with Sidecar URLs? they crash inappbrowser - image viewer is broken - touch event on inappbrowser? - muting a feed on non-rizzle accounts @@ -47,6 +48,7 @@ # Done +- ~~the thing with headlines disappearing~~ - ~~fix splash~~ - ~~replace face detection?~~ - ~~tweets don't display properly~~ diff --git a/app/sagas/check-buckets.js b/app/sagas/check-buckets.js new file mode 100644 index 00000000..b3e8e175 --- /dev/null +++ b/app/sagas/check-buckets.js @@ -0,0 +1,201 @@ +import SharedGroupPreferences from 'react-native-shared-group-preferences' +import { Clipboard } from "react-native" +import { isIgnoredUrl } from "../storage/async-storage" +import { parseString } from 'react-native-xml2js' +import log from '../utils/log' +import { call } from '@redux-saga/core/effects' + +const GROUP_NAME = 'group.com.adam-butler.rizzle' + +export function * checkBuckets () { + yield checkClipboard() + yield checkPageBucket() + yield checkFeedBucket() +} + +function * checkClipboard () { + console.log('Checking clipboard') + try { + let contents = yield call(Clipboard.getString) + // TODO make this more robust + if (contents.substring(0, 4) === 'http') { + const isIgnored = yield call(isIgnoredUrl, contents) + if (!isIgnored) { + yield showSavePageModal(contents, true) + } + } else if (contents.substring(0, 6) === '') { + } + } catch(err) { + log('checkClipboard', err) + } +} + +function * checkPageBucket () { + SharedGroupPreferences.getItem('page', GROUP_NAME).then(value => { + if (value !== null) { + SharedGroupPreferences.setItem('page', null, GROUP_NAME) + value = typeof value === 'object' ? + value[0] : + value + console.log(`Got a page to save: ${value}`) + showSavePageModal(value) + } + }).catch(err => { + // '1' just means that there is nothing in the bucket + if (err !== 1) { + log('checkPageBucket', err) + } + }) +} + +function * checkFeedBucket () { + SharedGroupPreferences.getItem('feed', GROUP_NAME).then(value => { + if (value !== null) { + const url = value + SharedGroupPreferences.setItem('feed', null, GROUP_NAME) + console.log(`Got a feed to subscribe to: ${url}`) + // TODO check that value is a feed url + // TODO check that feed is not already subscribed! + // right now it will just get ignored if it's already subscribed + // but it might be nice to say that in the message + fetch(url) + .then((response) => { + if (!response.ok) { + throw Error(response.statusText) + } + return response + }) + .then((response) => { + return response.text() + }) + .then((xml) => { + parseString(xml, (error, result) => { + if (error) { + throw error + } + let title, description + if (result.rss) { + title = typeof result.rss.channel[0].title[0] === 'string' ? + result.rss.channel[0].title[0] : + result.rss.channel[0].title[0]._ + description = result.rss.channel[0].description ? + (typeof result.rss.channel[0].description[0] === 'string' ? + result.rss.channel[0].description[0] : + result.rss.channel[0].description[0]._) : + '' + } else if (result.feed) { + // atom + title = typeof result.feed.title[0] === 'string' ? + result.feed.title[0] : + result.feed.title[0]._ + description = result.feed.subtitle ? + (typeof result.feed.subtitle[0] === 'string' ? + result.feed.subtitle[0] : + result.feed.subtitle[0]._) : + '' + } + showSaveFeedModal(url, title, description) + }) + }) + .catch(err => { + log('checkFeedBucket', err) + }) + } + }).catch(err => { + // '1' just means that there is nothing in the bucket + if (err !== 1) { + log('checkFeedBucket', err) + } + }) +} + +function * showSavePageModal (url, isClipboard = false) { + let displayUrl = url + if (displayUrl.length > 64) { + displayUrl = displayUrl.slice(0, 64) + '…' + } + let modalText = [ + { + text: 'Save this page?', + style: ['title'] + }, + { + text: displayUrl, + style: ['em'] + } + ] + if (isClipboard) { + modalText.push({ + text: 'This URL was in your clipboard. Copying a URL is an easy way to save a page in Rizzle.', + style: ['hint'] + }) + } + yield showModal({ + modalText, + modalHideCancel: false, + modalShow: true, + modalOnOk: () => { + saveURL(url) + } + }) +} + +function * showSaveFeedModal (url, title, description) { + yield showModal({ + modalText: [ + { + text: 'Add this feed?', + style: ['title'] + }, + { + text: title, + style: ['em'] + }, + { + text: description, + style: ['em', 'smaller'] + } + ], + modalHideCancel: false, + modalShow: true, + modalOnOk: () => { + addFeed({ + url, + title, + description + }) + fetchData() + } + }) +} + +function * showModal (modalProps) { + yield put({ + type: SHOW_MODAL, + modalProps + }) +} + +function * saveURL (url) { + yield put({ + type: SAVE_EXTERNAL_URL, + url + }) + yield put({ + type: SET_DISPLAY_MODE, + displayMode: ItemType.saved + }) +} + +function * addFeed (feed) { + yield put({ + type: ADD_FEED, + feed + }) +} + +function * fetchData () { + dispatch({ + type: FETCH_ITEMS + }) +} diff --git a/app/sagas/fetch-items.js b/app/sagas/fetch-items.js index 81b0818b..de3d6d7c 100644 --- a/app/sagas/fetch-items.js +++ b/app/sagas/fetch-items.js @@ -81,7 +81,7 @@ export function * fetchItems (type = ItemType.unread) { const oldItems = yield select(getItems, type) const lastUpdated = yield select(getLastUpdated, type) - if (lastUpdate === -1) { + if (lastUpdated === -1) { return } diff --git a/app/sagas/index.js b/app/sagas/index.js index 16661bd4..849d558b 100644 --- a/app/sagas/index.js +++ b/app/sagas/index.js @@ -28,11 +28,13 @@ import { SET_FEEDS } from '../store/feeds/types' import { + CHECK_BUCKETS, FETCH_ITEMS, ITEMS_SCREEN_BLUR, ITEMS_SCREEN_FOCUS } from '../store/ui/types' import { decorateItems } from './decorate-items' +import { checkBuckets } from './check-buckets' import { fetchAllItems, fetchUnreadItems } from './fetch-items' import { markLastItemRead, clearReadItems, filterItemsForRead } from './mark-read' import { pruneItems, removeItems, removeAllItems } from './prune-items' @@ -99,6 +101,7 @@ export function * initSagas () { yield takeEvery(UNSAVE_ITEM, inflateItems) yield takeEvery(SET_DISPLAY_MODE, inflateItems) yield takeEvery(UPDATE_CURRENT_INDEX, inflateItems) + yield takeEvery(CHECK_BUCKETS, checkBuckets) yield takeEvery(FETCH_ITEMS, clearReadItems) yield takeEvery(FETCH_ITEMS, fetchAllItems) yield takeEvery(CLEAR_READ_ITEMS, clearReadItems) diff --git a/app/store/ui/types.ts b/app/store/ui/types.ts index 37d70ea4..7e3a2251 100644 --- a/app/store/ui/types.ts +++ b/app/store/ui/types.ts @@ -38,6 +38,7 @@ export const HIDE_IMAGE_VIEWER = 'HIDE_IMAGE_VIEWER' export const TOGGLE_HIDE_MODAL = 'TOGGLE_HIDE_MODAL' export const FETCH_ITEMS = 'FETCH_ITEMS' export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS' +export const CHECK_BUCKETS = 'CHECK_BUCKETS' export const SET_DARK_MODE = 'SET_DARK_MODE' export const TOGGLE_DARK_MODE = 'TOGGLE_DARK_MODE' export const INCREASE_FONT_SIZE = 'INCREASE_FONT_SIZE' From d8c7a9ec270c7701178cca24860a65b9c9ade5eb Mon Sep 17 00:00:00 2001 From: Adam Butler Date: Mon, 21 Jun 2021 09:48:25 +0200 Subject: [PATCH 2/4] chore: update to use messages for saved pages --- app/sagas/check-buckets.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/app/sagas/check-buckets.js b/app/sagas/check-buckets.js index b3e8e175..3f91533c 100644 --- a/app/sagas/check-buckets.js +++ b/app/sagas/check-buckets.js @@ -38,7 +38,8 @@ function * checkPageBucket () { value[0] : value console.log(`Got a page to save: ${value}`) - showSavePageModal(value) + // TODO: figure out how to get the page title in the share extension + yield savePage({ url: value }) } }).catch(err => { // '1' just means that there is nothing in the bucket @@ -109,6 +110,11 @@ function * checkFeedBucket () { }) } +function * savePage (page) { + yield saveURL(page.url) + yield addMessage('Saved page: ' + (page.title ?? page.url)) +} + function * showSavePageModal (url, isClipboard = false) { let displayUrl = url if (displayUrl.length > 64) { @@ -181,10 +187,10 @@ function * saveURL (url) { type: SAVE_EXTERNAL_URL, url }) - yield put({ - type: SET_DISPLAY_MODE, - displayMode: ItemType.saved - }) + // yield put({ + // type: SET_DISPLAY_MODE, + // displayMode: ItemType.saved + // }) } function * addFeed (feed) { @@ -199,3 +205,11 @@ function * fetchData () { type: FETCH_ITEMS }) } + +function * addMessage (messageString) { + dispatch({ + type: ADD_MESSAGE, + messageString, + isSelfDestruct: true + }) +} From e03451334c1337f16056e4d57a9e0a9f5334df8f Mon Sep 17 00:00:00 2001 From: Adam Butler Date: Tue, 22 Jun 2021 09:27:20 +0200 Subject: [PATCH 3/4] chore: version bump --- app/ios/Reams.xcodeproj/project.pbxproj | 8 ++++---- app/ios/RizzleShare/Info.plist | 2 +- app/ios/rizzle/Info.plist | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/ios/Reams.xcodeproj/project.pbxproj b/app/ios/Reams.xcodeproj/project.pbxproj index 66e065f4..f1dcae52 100644 --- a/app/ios/Reams.xcodeproj/project.pbxproj +++ b/app/ios/Reams.xcodeproj/project.pbxproj @@ -863,7 +863,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 63; + CURRENT_PROJECT_VERSION = 64; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = LQ43Q9L9MY; ENABLE_BITCODE = NO; @@ -916,7 +916,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 63; + CURRENT_PROJECT_VERSION = 64; DEVELOPMENT_TEAM = LQ43Q9L9MY; HEADER_SEARCH_PATHS = ( "$(inherited)", @@ -976,7 +976,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 63; + CURRENT_PROJECT_VERSION = 64; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = LQ43Q9L9MY; ENABLE_BITCODE = NO; @@ -1039,7 +1039,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 63; + CURRENT_PROJECT_VERSION = 64; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = LQ43Q9L9MY; GCC_C_LANGUAGE_STANDARD = gnu11; diff --git a/app/ios/RizzleShare/Info.plist b/app/ios/RizzleShare/Info.plist index f63fac59..8be04e51 100644 --- a/app/ios/RizzleShare/Info.plist +++ b/app/ios/RizzleShare/Info.plist @@ -34,7 +34,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 63 + 64 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/app/ios/rizzle/Info.plist b/app/ios/rizzle/Info.plist index c64520fe..ffd7411d 100644 --- a/app/ios/rizzle/Info.plist +++ b/app/ios/rizzle/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 63 + 64 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes From 23d1fcd2599a912c190b01df0cd68a3a7873c441 Mon Sep 17 00:00:00 2001 From: Adam Butler Date: Fri, 3 Dec 2021 10:25:30 +0100 Subject: [PATCH 4/4] chore: include updates from AppStateListener --- app/sagas/check-buckets.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/app/sagas/check-buckets.js b/app/sagas/check-buckets.js index 3f91533c..8b48d435 100644 --- a/app/sagas/check-buckets.js +++ b/app/sagas/check-buckets.js @@ -1,9 +1,9 @@ import SharedGroupPreferences from 'react-native-shared-group-preferences' -import { Clipboard } from "react-native" -import { isIgnoredUrl } from "../storage/async-storage" +import Clipboard from "@react-native-community/clipboard" +import { isIgnoredUrl, addIgnoredUrl } from "../storage/async-storage" import { parseString } from 'react-native-xml2js' import log from '../utils/log' -import { call } from '@redux-saga/core/effects' +import { call, delay } from '@redux-saga/core/effects' const GROUP_NAME = 'group.com.adam-butler.rizzle' @@ -16,7 +16,12 @@ export function * checkBuckets () { function * checkClipboard () { console.log('Checking clipboard') try { + const hasUrl = yield call(Clipboard.hasURL) + if (!hasUrl) { + return + } let contents = yield call(Clipboard.getString) + contents = contents ?? '' // TODO make this more robust if (contents.substring(0, 4) === 'http') { const isIgnored = yield call(isIgnoredUrl, contents) @@ -34,12 +39,16 @@ function * checkPageBucket () { SharedGroupPreferences.getItem('page', GROUP_NAME).then(value => { if (value !== null) { SharedGroupPreferences.setItem('page', null, GROUP_NAME) - value = typeof value === 'object' ? - value[0] : - value - console.log(`Got a page to save: ${value}`) - // TODO: figure out how to get the page title in the share extension - yield savePage({ url: value }) + const parsed = JSON.parse(value) + const pages = typeof parsed === 'object' ? + parsed : + [parsed] + console.log(`Got ${pages.length} page${pages.length === 1 ? '' : 's'} to save`) + // ugh, need a timeout to allow for rehydration + yield delay(100) + pages.forEach(page => { + yield call(savePage, page) + }) } }).catch(err => { // '1' just means that there is nothing in the bucket @@ -111,7 +120,7 @@ function * checkFeedBucket () { } function * savePage (page) { - yield saveURL(page.url) + yield saveURL(page.url, page.title) yield addMessage('Saved page: ' + (page.title ?? page.url)) } @@ -141,7 +150,7 @@ function * showSavePageModal (url, isClipboard = false) { modalHideCancel: false, modalShow: true, modalOnOk: () => { - saveURL(url) + savePage(url) } }) }