├── .babelrc
├── .eslintrc
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── publish-github-release.yml
├── .gitignore
├── .node-version
├── LICENSE.md
├── README.md
├── TODO.md
├── build
├── common.js
├── electron.js
├── extension.js
├── web.js
└── xcode
│ └── Save to Raindrop.io
│ ├── App
│ ├── App.entitlements
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── icon_16.png
│ │ │ ├── icon_32.png
│ │ │ ├── safari_1024.png
│ │ │ ├── safari_128.png
│ │ │ ├── safari_256 1.png
│ │ │ ├── safari_256.png
│ │ │ ├── safari_32.png
│ │ │ ├── safari_512 1.png
│ │ │ ├── safari_512.png
│ │ │ └── safari_64.png
│ │ ├── Brand.imageset
│ │ │ ├── Contents.json
│ │ │ ├── icon_128.png
│ │ │ ├── icon_256.png
│ │ │ └── icon_384.png
│ │ ├── Contents.json
│ │ ├── Pro
│ │ │ ├── Contents.json
│ │ │ └── ProArt.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Group 4.png
│ │ │ │ ├── Group 4@2x.png
│ │ │ │ └── Group 4@3x.png
│ │ └── Welcome
│ │ │ ├── Browse.imageset
│ │ │ ├── Artboard.png
│ │ │ ├── Artboard@2x.png
│ │ │ ├── Artboard@3x.png
│ │ │ └── Contents.json
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── SubscribeViewController.swift
│ ├── WelcomeViewController.swift
│ ├── WindowController.swift
│ └── readme.md
│ ├── Extension
│ ├── Extension.entitlements
│ ├── Info.plist
│ └── SafariWebExtensionHandler.swift
│ ├── Save to Raindrop.io.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata
│ │ │ └── exentrich.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ ├── App.xcscheme
│ │ │ └── Extension.xcscheme
│ └── xcuserdata
│ │ └── exentrich.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
│ └── version.sh
├── functions
├── account
│ └── login.js
└── pb
│ ├── api
│ └── event.js
│ ├── hash.js.js
│ └── site.js.js
├── jsconfig.json
├── now.json
├── package-lock.json
├── package.json
├── src
├── assets
│ ├── _headers
│ ├── _redirects
│ ├── brand
│ │ ├── favicon.ico
│ │ ├── icon_128.png
│ │ ├── icon_16.png
│ │ ├── icon_180.png
│ │ ├── icon_32.png
│ │ ├── icon_48.png
│ │ ├── icon_512.png
│ │ ├── icon_64.png
│ │ ├── icon_raw.svg
│ │ ├── macos_512.png
│ │ ├── maskable_512.png
│ │ └── og.png
│ ├── icons
│ │ ├── add.svg
│ │ ├── add_active.svg
│ │ ├── add_tabs.svg
│ │ ├── ai.svg
│ │ ├── ai_active.svg
│ │ ├── alfred.svg
│ │ ├── android.svg
│ │ ├── android_active.svg
│ │ ├── apple.svg
│ │ ├── arrow.svg
│ │ ├── arrow_alt.svg
│ │ ├── article.svg
│ │ ├── article_active.svg
│ │ ├── audio.svg
│ │ ├── back.svg
│ │ ├── blank.svg
│ │ ├── book.svg
│ │ ├── broken.svg
│ │ ├── broken_active.svg
│ │ ├── cache_failed.svg
│ │ ├── cache_ready.svg
│ │ ├── cache_retry.svg
│ │ ├── calendar.svg
│ │ ├── check.svg
│ │ ├── check_active.svg
│ │ ├── clear.svg
│ │ ├── click.svg
│ │ ├── close.svg
│ │ ├── cloud.svg
│ │ ├── cloud_active.svg
│ │ ├── day.svg
│ │ ├── default_collection.svg
│ │ ├── default_collection_active.svg
│ │ ├── dev.svg
│ │ ├── dev_active.svg
│ │ ├── diamond.svg
│ │ ├── diamond_active.svg
│ │ ├── document.svg
│ │ ├── download.svg
│ │ ├── dropbox.svg
│ │ ├── dropbox_active.svg
│ │ ├── duplicates.svg
│ │ ├── duplicates_active.svg
│ │ ├── edit.svg
│ │ ├── edit_active.svg
│ │ ├── exit.svg
│ │ ├── export.svg
│ │ ├── export_active.svg
│ │ ├── extension.svg
│ │ ├── extension_active.svg
│ │ ├── facebook.svg
│ │ ├── fonts.svg
│ │ ├── fonts_active.svg
│ │ ├── fullscreen.svg
│ │ ├── fullscreen_active.svg
│ │ ├── gdrive.svg
│ │ ├── gdrive_active.svg
│ │ ├── github.svg
│ │ ├── google.svg
│ │ ├── help.svg
│ │ ├── hide.svg
│ │ ├── highlights.svg
│ │ ├── history.svg
│ │ ├── history_active.svg
│ │ ├── hotkey.svg
│ │ ├── ifttt.svg
│ │ ├── image.svg
│ │ ├── image_active.svg
│ │ ├── import.svg
│ │ ├── import_active.svg
│ │ ├── inbox.svg
│ │ ├── inbox_active.svg
│ │ ├── info.svg
│ │ ├── install.svg
│ │ ├── install_active.svg
│ │ ├── integrations.svg
│ │ ├── integrations_active.svg
│ │ ├── like.svg
│ │ ├── like_active.svg
│ │ ├── like_outline.svg
│ │ ├── link.svg
│ │ ├── link_active.svg
│ │ ├── lock.svg
│ │ ├── markdown.svg
│ │ ├── menu.svg
│ │ ├── micro_add.svg
│ │ ├── micro_ai.svg
│ │ ├── micro_arrow.svg
│ │ ├── micro_article.svg
│ │ ├── micro_audio.svg
│ │ ├── micro_book.svg
│ │ ├── micro_broken.svg
│ │ ├── micro_cache_failed.svg
│ │ ├── micro_calendar.svg
│ │ ├── micro_close.svg
│ │ ├── micro_cloud.svg
│ │ ├── micro_colapse.svg
│ │ ├── micro_comment.svg
│ │ ├── micro_document.svg
│ │ ├── micro_download.svg
│ │ ├── micro_duplicate.svg
│ │ ├── micro_edit.svg
│ │ ├── micro_expand.svg
│ │ ├── micro_image.svg
│ │ ├── micro_important.svg
│ │ ├── micro_important_active.svg
│ │ ├── micro_link.svg
│ │ ├── micro_list.svg
│ │ ├── micro_next.svg
│ │ ├── micro_open.svg
│ │ ├── micro_progress.svg
│ │ ├── micro_public.svg
│ │ ├── micro_reminder.svg
│ │ ├── micro_screenshot.svg
│ │ ├── micro_search.svg
│ │ ├── micro_tag.svg
│ │ ├── micro_tune.svg
│ │ ├── micro_user.svg
│ │ ├── micro_video.svg
│ │ ├── more.svg
│ │ ├── more_horizontal.svg
│ │ ├── move_to.svg
│ │ ├── move_to_active.svg
│ │ ├── new_bookmark.svg
│ │ ├── new_collection.svg
│ │ ├── new_collection_active.svg
│ │ ├── next.svg
│ │ ├── night.svg
│ │ ├── note.svg
│ │ ├── open.svg
│ │ ├── public.svg
│ │ ├── public_active.svg
│ │ ├── raindrop_logo.svg
│ │ ├── readwise.svg
│ │ ├── refresh.svg
│ │ ├── reminder.svg
│ │ ├── reminder_active.svg
│ │ ├── reminder_add.svg
│ │ ├── rss.svg
│ │ ├── search.svg
│ │ ├── select_all.svg
│ │ ├── select_none.svg
│ │ ├── settings.svg
│ │ ├── settings_active.svg
│ │ ├── share.svg
│ │ ├── sharing.svg
│ │ ├── sharing_active.svg
│ │ ├── show.svg
│ │ ├── show_active.svg
│ │ ├── sidebar.svg
│ │ ├── sidebar_alt.svg
│ │ ├── sidebar_alt_active.svg
│ │ ├── slack.svg
│ │ ├── sort_+lastUpdate.svg
│ │ ├── sort_+lastUpdate_active.svg
│ │ ├── sort_-created.svg
│ │ ├── sort_-created_active.svg
│ │ ├── sort_-domain.svg
│ │ ├── sort_-domain_active.svg
│ │ ├── sort_-lastUpdate.svg
│ │ ├── sort_-lastUpdate_active.svg
│ │ ├── sort_-title.svg
│ │ ├── sort_-title_active.svg
│ │ ├── sort_created.svg
│ │ ├── sort_created_active.svg
│ │ ├── sort_domain.svg
│ │ ├── sort_domain_active.svg
│ │ ├── sort_rating.svg
│ │ ├── sort_rating_active.svg
│ │ ├── sort_score.svg
│ │ ├── sort_score_active.svg
│ │ ├── sort_sort.svg
│ │ ├── sort_sort_active.svg
│ │ ├── sort_title.svg
│ │ ├── sort_title_active.svg
│ │ ├── tag.svg
│ │ ├── tag_active.svg
│ │ ├── trash.svg
│ │ ├── trash_active.svg
│ │ ├── twitter.svg
│ │ ├── twitter_outline.svg
│ │ ├── type.svg
│ │ ├── upload.svg
│ │ ├── upload_active.svg
│ │ ├── user.svg
│ │ ├── user_active.svg
│ │ ├── video.svg
│ │ ├── video_active.svg
│ │ ├── view_grid.svg
│ │ ├── view_grid_active.svg
│ │ ├── view_list.svg
│ │ ├── view_masonry.svg
│ │ ├── view_simple.svg
│ │ ├── vkontakte.svg
│ │ ├── web.svg
│ │ ├── web_active.svg
│ │ └── youtube.svg
│ ├── languages
│ │ ├── da.json
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es.json
│ │ ├── fi.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── index.json
│ │ ├── it.json
│ │ ├── ja.json
│ │ ├── ko.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt_BR.json
│ │ ├── ru.json
│ │ ├── sv.json
│ │ ├── tr.json
│ │ ├── zh-Hans.json
│ │ └── zh-Hant.json
│ ├── robots.txt
│ ├── sw.js
│ └── target
│ │ └── extension
│ │ ├── action_chrome_16.png
│ │ ├── action_chrome_24.png
│ │ ├── action_chrome_32.png
│ │ ├── action_firefox_dark_16.png
│ │ ├── action_firefox_dark_24.png
│ │ ├── action_firefox_dark_32.png
│ │ ├── action_firefox_light_16.png
│ │ ├── action_firefox_light_24.png
│ │ ├── action_firefox_light_32.png
│ │ ├── action_in_iframe.html
│ │ ├── action_in_iframe.js
│ │ ├── action_safari.png
│ │ ├── action_safari_16.png
│ │ ├── action_safari_19.png
│ │ ├── action_safari_32.png
│ │ ├── action_safari_38.png
│ │ └── welcome
│ │ ├── index.html
│ │ ├── logic.js
│ │ ├── logo-icon.webp
│ │ ├── modern-normalize.css
│ │ ├── screen-highlights.webp
│ │ ├── screen-save.webp
│ │ ├── screen-search.webp
│ │ ├── screen-sidepanel.webp
│ │ └── style.css
├── co
│ ├── bookmarks
│ │ ├── add
│ │ │ ├── extension
│ │ │ │ ├── highlights
│ │ │ │ │ └── index.js
│ │ │ │ ├── index.js
│ │ │ │ ├── more.js
│ │ │ │ ├── tab
│ │ │ │ │ ├── button.js
│ │ │ │ │ ├── button.module.styl
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── permission.js
│ │ │ │ └── tabs
│ │ │ │ │ └── index.js
│ │ │ ├── fallback
│ │ │ │ ├── file.js
│ │ │ │ ├── index.js
│ │ │ │ ├── link.js
│ │ │ │ └── link.module.css
│ │ │ └── index.js
│ │ ├── container
│ │ │ ├── index.js
│ │ │ ├── scroll.js
│ │ │ ├── scroll.module.styl
│ │ │ ├── wrap.js
│ │ │ └── wrap.module.styl
│ │ ├── dnd
│ │ │ ├── drag
│ │ │ │ └── item.js
│ │ │ └── drop.js
│ │ ├── edit
│ │ │ ├── crash.js
│ │ │ ├── form
│ │ │ │ ├── action
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── main.js
│ │ │ │ ├── collection
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── suggested.js
│ │ │ │ │ └── suggested.module.styl
│ │ │ │ ├── cover.js
│ │ │ │ ├── cover.module.styl
│ │ │ │ ├── date.js
│ │ │ │ ├── important.js
│ │ │ │ ├── index.js
│ │ │ │ ├── index.module.styl
│ │ │ │ ├── link.js
│ │ │ │ ├── note.js
│ │ │ │ ├── note.module.styl
│ │ │ │ ├── reminder.js
│ │ │ │ ├── tags
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── suggested.js
│ │ │ │ │ └── suggested.module.styl
│ │ │ │ └── title.js
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── export
│ │ │ ├── button.js
│ │ │ └── popover.js
│ │ ├── footer
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── header
│ │ │ ├── index.js
│ │ │ ├── regular
│ │ │ │ ├── icon.js
│ │ │ │ ├── icon.module.styl
│ │ │ │ ├── index.js
│ │ │ │ ├── index.module.styl
│ │ │ │ ├── more.js
│ │ │ │ ├── open.js
│ │ │ │ ├── rename.js
│ │ │ │ ├── sort.js
│ │ │ │ ├── title.js
│ │ │ │ └── view
│ │ │ │ │ ├── coverSize.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── menu.js
│ │ │ │ │ └── show.js
│ │ │ └── selectMode
│ │ │ │ ├── addTags.js
│ │ │ │ ├── cancel.js
│ │ │ │ ├── checkbox.js
│ │ │ │ ├── checkbox.module.styl
│ │ │ │ ├── index.js
│ │ │ │ ├── more.js
│ │ │ │ ├── move.js
│ │ │ │ ├── open.js
│ │ │ │ ├── remove.js
│ │ │ │ ├── title.js
│ │ │ │ └── working.js
│ │ ├── index.js
│ │ ├── index.module.styl
│ │ ├── item
│ │ │ ├── actions.js
│ │ │ ├── actions.module.styl
│ │ │ ├── contextmenu.js
│ │ │ ├── cover
│ │ │ │ ├── index.js
│ │ │ │ ├── size.js
│ │ │ │ ├── view.js
│ │ │ │ └── view.module.styl
│ │ │ ├── highlights.js
│ │ │ ├── highlights.module.styl
│ │ │ ├── index.js
│ │ │ ├── info
│ │ │ │ ├── index.js
│ │ │ │ ├── index.module.styl
│ │ │ │ ├── path.js
│ │ │ │ └── path.module.styl
│ │ │ ├── reminder.js
│ │ │ ├── reminder.module.styl
│ │ │ ├── tags.js
│ │ │ ├── tags.module.styl
│ │ │ ├── view.js
│ │ │ └── view.module.styl
│ │ ├── items
│ │ │ ├── empty
│ │ │ │ ├── index.js
│ │ │ │ ├── view.js
│ │ │ │ └── view.module.styl
│ │ │ ├── index.js
│ │ │ ├── sortable
│ │ │ │ ├── index.js
│ │ │ │ └── index.module.styl
│ │ │ └── view
│ │ │ │ ├── grid.js
│ │ │ │ ├── grid.module.styl
│ │ │ │ ├── index.js
│ │ │ │ ├── list.js
│ │ │ │ ├── list.module.styl
│ │ │ │ ├── masonry.js
│ │ │ │ ├── masonry.module.styl
│ │ │ │ ├── simple.js
│ │ │ │ └── simple.module.styl
│ │ └── openAll
│ │ │ └── index.js
│ ├── collections
│ │ ├── changeIcon
│ │ │ └── index.js
│ │ ├── group
│ │ │ ├── contextmenu.js
│ │ │ ├── index.js
│ │ │ ├── view.js
│ │ │ └── view.module.styl
│ │ ├── item
│ │ │ ├── accentColor.js
│ │ │ ├── blank.js
│ │ │ ├── contextmenu.js
│ │ │ ├── icon.js
│ │ │ ├── index.js
│ │ │ ├── rename.js
│ │ │ ├── title.js
│ │ │ ├── title.module.styl
│ │ │ ├── view.js
│ │ │ └── view.module.styl
│ │ ├── items
│ │ │ ├── empty.js
│ │ │ ├── index.js
│ │ │ ├── index.module.styl
│ │ │ ├── selectMode.js
│ │ │ ├── selectMode.module.styl
│ │ │ ├── tree.js
│ │ │ └── tree.module.styl
│ │ ├── picker
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ └── sharing
│ │ │ ├── collaborators
│ │ │ ├── index.js
│ │ │ ├── invite
│ │ │ │ ├── index.js
│ │ │ │ └── view.js
│ │ │ ├── user.js
│ │ │ └── view.js
│ │ │ ├── index.js
│ │ │ ├── index.module.styl
│ │ │ ├── public
│ │ │ ├── customization.js
│ │ │ ├── enable.js
│ │ │ ├── footer.js
│ │ │ ├── index.js
│ │ │ └── link.js
│ │ │ ├── tabs
│ │ │ └── index.js
│ │ │ └── title
│ │ │ ├── index.js
│ │ │ └── view.js
│ ├── common
│ │ ├── alert
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── button
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── footer
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── form
│ │ │ ├── checkbox.js
│ │ │ ├── checkbox.module.styl
│ │ │ ├── date_time.js
│ │ │ ├── date_time.module.styl
│ │ │ ├── group.js
│ │ │ ├── group.module.styl
│ │ │ ├── index.js
│ │ │ ├── label.js
│ │ │ ├── label.module.styl
│ │ │ ├── layout.js
│ │ │ ├── layout.module.styl
│ │ │ ├── progress.js
│ │ │ ├── progress.module.styl
│ │ │ ├── radio.js
│ │ │ ├── radio.module.styl
│ │ │ ├── range.js
│ │ │ ├── range.module.styl
│ │ │ ├── search.js
│ │ │ ├── search.module.styl
│ │ │ ├── separator.js
│ │ │ ├── separator.module.styl
│ │ │ ├── sub_label.js
│ │ │ ├── sub_label.module.styl
│ │ │ ├── text.js
│ │ │ ├── text.module.styl
│ │ │ ├── title.js
│ │ │ ├── title.module.styl
│ │ │ ├── toggle.js
│ │ │ └── toggle.module.styl
│ │ ├── header
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── icon
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── list
│ │ │ ├── index.js
│ │ │ ├── item.js
│ │ │ ├── item.module.styl
│ │ │ ├── section.js
│ │ │ ├── section.module.styl
│ │ │ ├── separator.js
│ │ │ ├── separator.module.styl
│ │ │ ├── wrap.js
│ │ │ └── wrap.module.styl
│ │ ├── preloader
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── safeHtml.js
│ │ ├── safeMarkdown.js
│ │ ├── select
│ │ │ ├── index.js
│ │ │ ├── multi
│ │ │ │ ├── index.js
│ │ │ │ └── index.module.styl
│ │ │ └── single
│ │ │ │ └── index.js
│ │ ├── slider
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── superImg.js
│ │ ├── superLink
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── superOverflow.js
│ │ ├── tabs
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── webview
│ │ │ ├── browser.js
│ │ │ ├── browser.module.styl
│ │ │ ├── electron.js
│ │ │ ├── electron.module.styl
│ │ │ ├── error.js
│ │ │ ├── error.module.styl
│ │ │ ├── index.js
│ │ │ ├── index.module.styl
│ │ │ ├── preloader.js
│ │ │ └── preloader.module.styl
│ │ └── withPause.js
│ ├── filters
│ │ ├── item
│ │ │ ├── index.js
│ │ │ ├── index.module.styl
│ │ │ └── useItemInfo.js
│ │ └── section
│ │ │ ├── contextmenu.js
│ │ │ ├── index.js
│ │ │ ├── view.js
│ │ │ └── view.module.styl
│ ├── highlights
│ │ ├── copy-button
│ │ │ └── index.js
│ │ ├── export-button
│ │ │ └── index.js
│ │ ├── item
│ │ │ ├── color.js
│ │ │ ├── color.module.styl
│ │ │ ├── index.js
│ │ │ ├── view.js
│ │ │ └── view.module.styl
│ │ ├── items
│ │ │ ├── index.js
│ │ │ ├── index.module.styl
│ │ │ └── useScrollToNew.js
│ │ ├── text
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ └── useWithWebView
│ │ │ ├── index.js
│ │ │ └── messaging.js
│ ├── overlay
│ │ ├── dialog
│ │ │ ├── index.js
│ │ │ └── view
│ │ │ │ ├── alert.js
│ │ │ │ ├── alert.module.styl
│ │ │ │ ├── confirm.js
│ │ │ │ ├── confirm.module.styl
│ │ │ │ ├── loading.js
│ │ │ │ ├── loading.module.styl
│ │ │ │ ├── prompt.js
│ │ │ │ └── prompt.module.styl
│ │ ├── modal
│ │ │ ├── content.js
│ │ │ ├── content.module.styl
│ │ │ ├── context.js
│ │ │ ├── header.js
│ │ │ ├── header.module.styl
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ └── popover
│ │ │ ├── context.js
│ │ │ ├── index.js
│ │ │ ├── index.module.styl
│ │ │ ├── menu
│ │ │ ├── index.js
│ │ │ ├── index.module.styl
│ │ │ ├── item.js
│ │ │ ├── item.module.styl
│ │ │ ├── section.js
│ │ │ ├── separator.js
│ │ │ └── separator.module.styl
│ │ │ └── more
│ │ │ └── index.js
│ ├── picker
│ │ ├── file
│ │ │ ├── base
│ │ │ │ ├── index.js
│ │ │ │ ├── progress.js
│ │ │ │ └── progress.module.styl
│ │ │ ├── drop
│ │ │ │ ├── index.js
│ │ │ │ └── module.js
│ │ │ └── element
│ │ │ │ └── index.js
│ │ ├── icon
│ │ │ ├── add.js
│ │ │ ├── index.js
│ │ │ ├── items.js
│ │ │ ├── items.module.styl
│ │ │ └── reset.js
│ │ ├── image
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── link
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ └── tags
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ ├── screen
│ │ ├── basic
│ │ │ ├── header.js
│ │ │ ├── index.js
│ │ │ ├── index.module.css
│ │ │ ├── size.module.css
│ │ │ └── themes.module.css
│ │ ├── error
│ │ │ └── index.js
│ │ └── splitview
│ │ │ ├── helpers
│ │ │ ├── small.js
│ │ │ └── small.module.styl
│ │ │ ├── index.js
│ │ │ ├── index.module.styl
│ │ │ ├── main
│ │ │ ├── content.js
│ │ │ ├── content.module.styl
│ │ │ ├── footer.js
│ │ │ ├── header.js
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ │ ├── reader
│ │ │ ├── content.js
│ │ │ ├── content.module.styl
│ │ │ ├── footer.js
│ │ │ ├── header.js
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ │ └── sidebar
│ │ │ ├── backdrop.js
│ │ │ ├── content.js
│ │ │ ├── content.module.styl
│ │ │ ├── footer.js
│ │ │ ├── header.js
│ │ │ ├── index.js
│ │ │ ├── index.module.styl
│ │ │ ├── resize.js
│ │ │ └── resize.module.styl
│ ├── search
│ │ ├── field
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── form
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── index.js
│ │ ├── menu
│ │ │ ├── help
│ │ │ │ ├── index.js
│ │ │ │ └── index.module.styl
│ │ │ ├── incollection
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ ├── recent
│ │ │ │ ├── index.js
│ │ │ │ ├── item.js
│ │ │ │ ├── item.module.styl
│ │ │ │ ├── section.js
│ │ │ │ └── section.module.styl
│ │ │ └── suggestions
│ │ │ │ ├── index.js
│ │ │ │ ├── tip.js
│ │ │ │ └── tip.module.styl
│ │ ├── useDownshift.js
│ │ ├── useFilterValue.js
│ │ ├── useMenuItems.js
│ │ └── useSpaceId.js
│ ├── tags
│ │ ├── autocomplete
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── field
│ │ │ └── index.js
│ │ ├── item
│ │ │ ├── contextmenu.js
│ │ │ ├── icon.js
│ │ │ ├── icon.module.styl
│ │ │ ├── index.js
│ │ │ ├── rename.js
│ │ │ ├── view.js
│ │ │ └── view.module.styl
│ │ └── section
│ │ │ ├── contextmenu.js
│ │ │ ├── index.js
│ │ │ ├── view.js
│ │ │ └── view.module.styl
│ ├── user
│ │ ├── about
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── profile
│ │ │ ├── button.js
│ │ │ ├── index.js
│ │ │ └── menu.js
│ │ ├── upgrade
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ └── withEdit.js
│ └── virtual
│ │ ├── helpers
│ │ └── withAutoSize.js
│ │ ├── lazy
│ │ ├── dimension.js
│ │ ├── index.js
│ │ ├── item.js
│ │ ├── item.module.css
│ │ ├── readme.md
│ │ └── visibility.js
│ │ └── tree
│ │ ├── index.js
│ │ └── index.module.styl
├── config
│ ├── csp.js
│ ├── index.js
│ ├── links.js
│ └── vendors.js
├── data
│ ├── README.md
│ ├── TODO.md
│ ├── actions
│ │ ├── backups.js
│ │ ├── bookmarks
│ │ │ ├── draft.js
│ │ │ ├── highlights.js
│ │ │ ├── html.js
│ │ │ ├── index.js
│ │ │ ├── recent.js
│ │ │ ├── selectMode.js
│ │ │ ├── single.js
│ │ │ └── space.js
│ │ ├── collections.js
│ │ ├── config.js
│ │ ├── covers.js
│ │ ├── filters.js
│ │ ├── import.js
│ │ ├── oauth.js
│ │ ├── predictions.js
│ │ ├── rate.js
│ │ ├── tags.js
│ │ └── user.js
│ ├── constants
│ │ ├── app.js
│ │ ├── backups.js
│ │ ├── bookmarks.js
│ │ ├── collections.js
│ │ ├── config.js
│ │ ├── covers.js
│ │ ├── filters.js
│ │ ├── import.js
│ │ ├── oauth.js
│ │ ├── predictions.js
│ │ ├── rate.js
│ │ ├── tags.js
│ │ └── user.js
│ ├── helpers
│ │ ├── bookmarks
│ │ │ ├── blankSpace.js
│ │ │ ├── getUrl.js
│ │ │ ├── index.js
│ │ │ ├── normalizeRecentSearch.js
│ │ │ └── queryIsEqual.js
│ │ ├── collections.js
│ │ ├── colors.js
│ │ ├── defaults.js
│ │ ├── filters
│ │ │ ├── blankSpace.js
│ │ │ ├── index.js
│ │ │ └── normalizeItems.js
│ │ ├── oauth
│ │ │ ├── blankClient.js
│ │ │ ├── index.js
│ │ │ └── normalizeClient.js
│ │ ├── tags
│ │ │ ├── blankSpace.js
│ │ │ ├── index.js
│ │ │ ├── normalizeTag.js
│ │ │ └── normalizeTags.js
│ │ └── user.js
│ ├── index.js
│ ├── modules
│ │ ├── api.js
│ │ ├── error.js
│ │ ├── format
│ │ │ ├── cache_url.js
│ │ │ ├── domain.js
│ │ │ ├── favicon.js
│ │ │ ├── iframeable_url.js
│ │ │ ├── screenshot.js
│ │ │ ├── thumb.js
│ │ │ └── url.js
│ │ ├── persistConfig.js
│ │ └── user
│ │ │ └── isPro.js
│ ├── reducers
│ │ ├── backups.js
│ │ ├── bookmarks
│ │ │ ├── draft.js
│ │ │ ├── highlights.js
│ │ │ ├── html.js
│ │ │ ├── index.js
│ │ │ ├── recent.js
│ │ │ ├── selectMode.js
│ │ │ ├── single.js
│ │ │ ├── sort.js
│ │ │ ├── space.js
│ │ │ └── utils.js
│ │ ├── collections
│ │ │ ├── defaults.js
│ │ │ ├── drafts.js
│ │ │ ├── groups.js
│ │ │ ├── index.js
│ │ │ ├── items.js
│ │ │ ├── reorder.js
│ │ │ ├── selectMode.js
│ │ │ ├── sharing.js
│ │ │ ├── single.js
│ │ │ └── utils.js
│ │ ├── config.js
│ │ ├── covers.js
│ │ ├── filters.js
│ │ ├── import.js
│ │ ├── index.js
│ │ ├── notes.md
│ │ ├── oauth.js
│ │ ├── predictions.js
│ │ ├── rate.js
│ │ ├── tags
│ │ │ ├── index.js
│ │ │ ├── items.js
│ │ │ ├── recent.js
│ │ │ └── single.js
│ │ └── user.js
│ ├── sagas
│ │ ├── backups.js
│ │ ├── bookmarks
│ │ │ ├── draft.js
│ │ │ ├── highlights.js
│ │ │ ├── html.js
│ │ │ ├── index.js
│ │ │ ├── recent.js
│ │ │ ├── selectMode.js
│ │ │ ├── single.js
│ │ │ └── space.js
│ │ ├── collections
│ │ │ ├── drafts.js
│ │ │ ├── groups.js
│ │ │ ├── index.js
│ │ │ ├── items.js
│ │ │ ├── selectMode.js
│ │ │ ├── sharing.js
│ │ │ └── single.js
│ │ ├── common.js
│ │ ├── config.js
│ │ ├── covers.js
│ │ ├── filters.js
│ │ ├── import.js
│ │ ├── index.js
│ │ ├── oauth.js
│ │ ├── predictions.js
│ │ ├── tags
│ │ │ ├── index.js
│ │ │ ├── items.js
│ │ │ ├── recent.js
│ │ │ └── single.js
│ │ └── user.js
│ ├── selectors
│ │ ├── bookmarks
│ │ │ ├── draft.js
│ │ │ ├── html.js
│ │ │ ├── index.js
│ │ │ ├── selectMode.js
│ │ │ ├── single.js
│ │ │ └── space.js
│ │ ├── collections
│ │ │ ├── drafts.js
│ │ │ ├── groups.js
│ │ │ ├── index.js
│ │ │ ├── items.js
│ │ │ ├── selectMode.js
│ │ │ ├── sharing.js
│ │ │ └── single.js
│ │ ├── filters
│ │ │ ├── index.js
│ │ │ ├── items.js
│ │ │ └── search.js
│ │ ├── oauth
│ │ │ ├── connections.js
│ │ │ ├── index.js
│ │ │ └── my.js
│ │ ├── search
│ │ │ ├── collections.js
│ │ │ ├── index.js
│ │ │ ├── options.js
│ │ │ ├── recent.js
│ │ │ └── suggestions.js
│ │ ├── tags
│ │ │ ├── autocomplete.js
│ │ │ ├── index.js
│ │ │ ├── items.js
│ │ │ └── search.js
│ │ └── user.js
│ └── utils
│ │ ├── authStatus.js
│ │ └── wrapFunc.js
├── index.ejs
├── index.js
├── local
│ ├── actions
│ │ ├── app.js
│ │ ├── index.js
│ │ └── pause.js
│ ├── constants
│ │ ├── app.js
│ │ ├── index.js
│ │ └── pause.js
│ └── reducers
│ │ ├── app.js
│ │ ├── index.js
│ │ └── pause.js
├── modules
│ ├── browser
│ │ ├── copyText.js
│ │ ├── eventOrder.js
│ │ ├── index.js
│ │ ├── resizeObserver.js
│ │ └── scrollbarIsObtrusive.js
│ ├── format
│ │ ├── callback
│ │ │ ├── debounce.js
│ │ │ └── use-debounce.js
│ │ ├── color
│ │ │ └── hextToHsl.js
│ │ ├── date
│ │ │ ├── index.js
│ │ │ ├── long.js
│ │ │ ├── longTime.js
│ │ │ ├── month.js
│ │ │ ├── numeric.js
│ │ │ ├── parse.js
│ │ │ ├── short.js
│ │ │ └── shortTime.js
│ │ ├── file
│ │ │ ├── dataURItoFile.js
│ │ │ └── index.js
│ │ ├── number
│ │ │ ├── compact.js
│ │ │ ├── fileSize.js
│ │ │ └── index.js
│ │ ├── string
│ │ │ ├── codeToLanguage.js
│ │ │ └── index.js
│ │ └── url
│ │ │ ├── extractURLs.js
│ │ │ ├── getDomain.js
│ │ │ ├── index.js
│ │ │ ├── isSPA.js
│ │ │ └── normalizeURL.js
│ ├── localStorage
│ │ └── index.js
│ ├── sessionStorage
│ │ └── index.js
│ ├── sw
│ │ └── component.js
│ ├── translate
│ │ ├── component.js
│ │ └── index.js
│ └── vendors
│ │ └── sentry
│ │ ├── component.js
│ │ └── index.js
├── routes
│ ├── _app
│ │ ├── extension
│ │ │ ├── index.js
│ │ │ ├── useBookmarksChanged.js
│ │ │ └── useExternalLinks.js
│ │ ├── fallback.js
│ │ └── index.js
│ ├── _document
│ │ ├── body.js
│ │ ├── html.js
│ │ ├── html.module.css
│ │ ├── index.js
│ │ └── normalize.css
│ ├── _protected
│ │ └── index.js
│ ├── _splash
│ │ ├── extension.js
│ │ ├── fallback.js
│ │ └── index.js
│ ├── account
│ │ ├── electron
│ │ │ └── index.js
│ │ ├── extension
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── index.js
│ │ ├── jwt
│ │ │ └── index.js
│ │ ├── layout.js
│ │ ├── layout.module.styl
│ │ ├── login
│ │ │ └── index.js
│ │ ├── lost
│ │ │ └── index.js
│ │ ├── recover
│ │ │ └── index.js
│ │ ├── redirect.js
│ │ ├── signup
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── social
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ └── tfa
│ │ │ ├── login.js
│ │ │ └── revoke.js
│ ├── add
│ │ ├── content.js
│ │ ├── events.js
│ │ ├── header
│ │ │ ├── close.js
│ │ │ ├── index.js
│ │ │ ├── index.module.styl
│ │ │ └── title.js
│ │ └── index.js
│ ├── default.js
│ ├── extension
│ │ ├── clipper
│ │ │ ├── buttons.js
│ │ │ ├── content.js
│ │ │ ├── header.js
│ │ │ ├── header.module.styl
│ │ │ └── index.js
│ │ ├── highlights
│ │ │ ├── bookmark
│ │ │ │ ├── header.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.module.styl
│ │ │ ├── empty
│ │ │ │ ├── assets
│ │ │ │ │ └── safari-ios.png
│ │ │ │ ├── howto.js
│ │ │ │ ├── howto.module.styl
│ │ │ │ ├── index.js
│ │ │ │ └── permissions.js
│ │ │ └── index.js
│ │ ├── index.extension.js
│ │ ├── index.js
│ │ └── tabs
│ │ │ ├── action.js
│ │ │ ├── close.js
│ │ │ ├── collection.js
│ │ │ ├── header.js
│ │ │ ├── index.js
│ │ │ ├── list.js
│ │ │ ├── list.module.styl
│ │ │ ├── tags.js
│ │ │ ├── useSubmit.js
│ │ │ └── useTabs.js
│ ├── index.js
│ ├── join
│ │ ├── index.js
│ │ └── layout.module.styl
│ ├── my
│ │ ├── index.js
│ │ ├── item
│ │ │ ├── highlights
│ │ │ │ ├── collapsed.js
│ │ │ │ ├── expanded.js
│ │ │ │ ├── expanded.module.styl
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ ├── layout.js
│ │ │ ├── notFound.js
│ │ │ ├── tab
│ │ │ │ ├── any.js
│ │ │ │ ├── cache.js
│ │ │ │ ├── cache.module.styl
│ │ │ │ ├── edit.js
│ │ │ │ ├── preview.js
│ │ │ │ ├── preview.module.styl
│ │ │ │ ├── web.js
│ │ │ │ └── web.module.styl
│ │ │ └── toolbar
│ │ │ │ ├── index.js
│ │ │ │ ├── settings.js
│ │ │ │ └── settings.module.styl
│ │ ├── layout.js
│ │ ├── onboard-upgrade.js
│ │ ├── sidebar
│ │ │ ├── filters_tags.js
│ │ │ ├── index.js
│ │ │ ├── profile
│ │ │ │ ├── index.js
│ │ │ │ └── index.module.styl
│ │ │ └── upgrade
│ │ │ │ ├── index.js
│ │ │ │ ├── index.module.styl
│ │ │ │ └── pic.svg
│ │ └── space
│ │ │ ├── bookmarks.js
│ │ │ ├── header
│ │ │ ├── add.js
│ │ │ ├── index.js
│ │ │ └── share.js
│ │ │ ├── index.js
│ │ │ └── search_in
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ ├── notFound.js
│ ├── settings
│ │ ├── account
│ │ │ ├── connect.js
│ │ │ ├── connect.module.styl
│ │ │ ├── index.js
│ │ │ ├── logoutAll.js
│ │ │ ├── profile
│ │ │ │ ├── avatar.js
│ │ │ │ ├── avatar.module.styl
│ │ │ │ ├── index.js
│ │ │ │ ├── password.js
│ │ │ │ └── password.module.styl
│ │ │ ├── remove.js
│ │ │ ├── reset.js
│ │ │ └── usage.js
│ │ ├── app
│ │ │ ├── ai.js
│ │ │ ├── broken_level.js
│ │ │ ├── default_collection_view.js
│ │ │ ├── index.js
│ │ │ ├── lang.js
│ │ │ ├── lang.module.styl
│ │ │ ├── nested_view_legacy.js
│ │ │ ├── raindrops_buttons.js
│ │ │ ├── raindrops_click.js
│ │ │ ├── raindrops_search_by_score.js
│ │ │ ├── size.js
│ │ │ ├── tags_sort.js
│ │ │ ├── theme.js
│ │ │ └── theme.module.styl
│ │ ├── backups
│ │ │ ├── add.js
│ │ │ ├── automatic.js
│ │ │ ├── cloud.js
│ │ │ ├── cloud.module.styl
│ │ │ ├── files
│ │ │ │ ├── index.js
│ │ │ │ └── item.js
│ │ │ ├── index.js
│ │ │ └── index.module.styl
│ │ ├── extension
│ │ │ ├── add
│ │ │ │ └── index.js
│ │ │ ├── browser_extension_mode
│ │ │ │ ├── clipper.svg
│ │ │ │ ├── index.js
│ │ │ │ ├── index.module.styl
│ │ │ │ └── mini_app.svg
│ │ │ ├── hotkeys
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ └── permissions
│ │ │ │ └── index.js
│ │ ├── import
│ │ │ ├── file.js
│ │ │ ├── file.module.styl
│ │ │ ├── help.js
│ │ │ ├── index.js
│ │ │ ├── mode.js
│ │ │ └── parcel.js
│ │ ├── index.js
│ │ ├── integrations
│ │ │ ├── connections
│ │ │ │ ├── index.js
│ │ │ │ └── index.module.styl
│ │ │ ├── dev
│ │ │ │ ├── create.js
│ │ │ │ ├── edit.js
│ │ │ │ ├── edit.module.styl
│ │ │ │ ├── help.js
│ │ │ │ ├── index.js
│ │ │ │ ├── index.module.styl
│ │ │ │ └── my.js
│ │ │ ├── index.js
│ │ │ └── ready
│ │ │ │ ├── index.js
│ │ │ │ ├── index.module.styl
│ │ │ │ └── item.js
│ │ ├── layout.js
│ │ ├── layout.module.styl
│ │ ├── pro
│ │ │ ├── free
│ │ │ │ ├── index.js
│ │ │ │ └── index.module.styl
│ │ │ ├── index.js
│ │ │ └── paid
│ │ │ │ ├── actions.js
│ │ │ │ ├── actions.module.styl
│ │ │ │ ├── alert.js
│ │ │ │ ├── alert.module.styl
│ │ │ │ ├── help.js
│ │ │ │ ├── help.module.styl
│ │ │ │ ├── index.js
│ │ │ │ ├── period.js
│ │ │ │ ├── period.module.styl
│ │ │ │ ├── price.js
│ │ │ │ ├── price.module.styl
│ │ │ │ ├── status.js
│ │ │ │ └── status.module.styl
│ │ ├── sidebar
│ │ │ ├── app.js
│ │ │ ├── header.js
│ │ │ ├── index.js
│ │ │ ├── index.module.styl
│ │ │ ├── pages.js
│ │ │ ├── user.js
│ │ │ └── user.module.styl
│ │ └── tfa
│ │ │ ├── enable.js
│ │ │ ├── help.js
│ │ │ ├── index.js
│ │ │ ├── revoke.js
│ │ │ └── status.js
│ └── suggestions
│ │ ├── index.js
│ │ ├── index.module.css
│ │ ├── intro
│ │ ├── index.js
│ │ ├── index.module.styl
│ │ └── intro.png
│ │ └── predictions
│ │ ├── empty.js
│ │ ├── index.js
│ │ ├── mergetags.js
│ │ ├── move.js
│ │ ├── raindrops-list.js
│ │ ├── style.module.styl
│ │ └── tag.js
├── target
│ ├── extension
│ │ ├── background
│ │ │ ├── action.js
│ │ │ ├── commands.js
│ │ │ ├── contextMenus.js
│ │ │ ├── fix-safari-permissions.js
│ │ │ ├── highlights
│ │ │ │ ├── README.md
│ │ │ │ ├── highlight.js
│ │ │ │ ├── index.js
│ │ │ │ └── logic.js
│ │ │ ├── index.js
│ │ │ ├── links.js
│ │ │ ├── omnibox.js
│ │ │ ├── popup.js
│ │ │ ├── runtime.js
│ │ │ └── storage.js
│ │ ├── browser.js
│ │ ├── captureTab.js
│ │ ├── currentTab.js
│ │ ├── environment.js
│ │ ├── getLanguage.js
│ │ ├── getMeta
│ │ │ ├── index.js
│ │ │ └── parse.js
│ │ ├── hotkeys.js
│ │ ├── index.js
│ │ ├── manifest
│ │ │ ├── index.js
│ │ │ └── locales.js
│ │ └── openTab.js
│ ├── fallback
│ │ ├── captureTab.js
│ │ ├── environment.js
│ │ ├── getLanguage.js
│ │ ├── getMeta.js
│ │ ├── hotkeys.js
│ │ ├── index.js
│ │ └── openTab.js
│ └── index.js
└── wdyr.js
└── vercel.json
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | Do not post feature requests here please!
11 | We have a dedicated website for them https://raindropio.canny.io/feature-requests
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | .DS_Store
3 | __MACOSX
4 |
5 | # Node
6 | node_modules
7 | yarn-error.log
8 | npm-debug.log
9 | .yarn
10 |
11 | # Next
12 | dist
13 |
14 | # Xcode
15 | #
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 | xcuserdata
25 | *.xccheckout
26 | *.moved-aside
27 | DerivedData
28 | *.hmap
29 | *.ipa
30 | *.xcuserstate
31 | project.xcworkspace
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | 18.16.0
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/App.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Save to Raindrop.io
4 | //
5 | // Created by Rustem Mussabekov on 18.09.2020.
6 | //
7 |
8 | import Cocoa
9 |
10 | @NSApplicationMain
11 | class AppDelegate: NSObject, NSApplicationDelegate {
12 |
13 | func applicationDidFinishLaunching(_ aNotification: Notification) {
14 | // Insert code here to initialize your application
15 | }
16 |
17 | func applicationWillTerminate(_ aNotification: Notification) {
18 | // Insert code here to tear down your application
19 | }
20 |
21 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
22 | return true
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/icon_16.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/icon_32.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_1024.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_128.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_256 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_256 1.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_256.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_32.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_512 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_512 1.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_512.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/AppIcon.appiconset/safari_64.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Brand.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_128.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "icon_256.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "icon_384.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Brand.imageset/icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Brand.imageset/icon_128.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Brand.imageset/icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Brand.imageset/icon_256.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Brand.imageset/icon_384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Brand.imageset/icon_384.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Pro/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Pro/ProArt.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 4.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Group 4@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Group 4@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Pro/ProArt.imageset/Group 4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Pro/ProArt.imageset/Group 4.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Pro/ProArt.imageset/Group 4@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Pro/ProArt.imageset/Group 4@2x.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Pro/ProArt.imageset/Group 4@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Pro/ProArt.imageset/Group 4@3x.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Welcome/Browse.imageset/Artboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Welcome/Browse.imageset/Artboard.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Welcome/Browse.imageset/Artboard@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Welcome/Browse.imageset/Artboard@2x.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Welcome/Browse.imageset/Artboard@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Welcome/Browse.imageset/Artboard@3x.png
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Welcome/Browse.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Artboard.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Artboard@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Artboard@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/Assets.xcassets/Welcome/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/App/readme.md:
--------------------------------------------------------------------------------
1 | # Subscribe
2 | https://api.raindrop.io/v1/auth/jwt?done_uri=rniomacsafari://subscribe
3 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/Extension/Extension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/Save to Raindrop.io.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/Save to Raindrop.io.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/Save to Raindrop.io.xcodeproj/project.xcworkspace/xcuserdata/exentrich.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/build/xcode/Save to Raindrop.io/Save to Raindrop.io.xcodeproj/project.xcworkspace/xcuserdata/exentrich.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/build/xcode/Save to Raindrop.io/version.sh:
--------------------------------------------------------------------------------
1 | if [[ "$OSTYPE" == "darwin"* ]]; then
2 | PACKAGE_VERSION=$(cat ../../../package.json \
3 | | grep version \
4 | | head -1 \
5 | | awk -F: '{ print $2 }' \
6 | | sed 's/[",]//g')
7 | echo $PACKAGE_VERSION
8 | agvtool new-marketing-version $PACKAGE_VERSION
9 | agvtool next-version -all
10 | sed -i '' "s/MARKETING_VERSION = [0-9]*\.[0-9]*\.[0-9]*/MARKETING_VERSION =$PACKAGE_VERSION/" "Save to Raindrop.io.xcodeproj/project.pbxproj"
11 | else
12 | echo "This script can only run on macOS!"
13 | fi
--------------------------------------------------------------------------------
/functions/account/login.js:
--------------------------------------------------------------------------------
1 | export async function onRequest({ request }) {
2 | const res = await fetch(request)
3 |
4 | return new Response(res.body, {
5 | status: 401,
6 | headers: res.headers
7 | })
8 | }
--------------------------------------------------------------------------------
/functions/pb/api/event.js:
--------------------------------------------------------------------------------
1 | export async function onRequest({ request }) {
2 | return fetch('https://plausible.io/api/event', request)
3 | }
--------------------------------------------------------------------------------
/functions/pb/hash.js.js:
--------------------------------------------------------------------------------
1 | export async function onRequest({ request }) {
2 | return fetch('https://plausible.io/js/plausible.hash.exclusions.js', request)
3 | }
--------------------------------------------------------------------------------
/functions/pb/site.js.js:
--------------------------------------------------------------------------------
1 | export async function onRequest({ request }) {
2 | return fetch('https://plausible.io/js/plausible.exclusions.js', request)
3 | }
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "~t": ["src/modules/translate"],
6 | "~*": ["src/*"]
7 | }
8 | },
9 | "include": ["src/**/*"]
10 | }
--------------------------------------------------------------------------------
/src/assets/_headers:
--------------------------------------------------------------------------------
1 | /assets/*
2 | Cache-Control: public,max-age=31536000,immutable
3 |
4 | /*
5 | X-Frame-Options: DENY
--------------------------------------------------------------------------------
/src/assets/_redirects:
--------------------------------------------------------------------------------
1 | /legacy/4 https://extension.raindrop.io
--------------------------------------------------------------------------------
/src/assets/brand/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/brand/favicon.ico
--------------------------------------------------------------------------------
/src/assets/brand/icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/brand/icon_128.png
--------------------------------------------------------------------------------
/src/assets/brand/icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/brand/icon_16.png
--------------------------------------------------------------------------------
/src/assets/brand/icon_180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/brand/icon_180.png
--------------------------------------------------------------------------------
/src/assets/brand/icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/brand/icon_32.png
--------------------------------------------------------------------------------
/src/assets/brand/icon_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/brand/icon_48.png
--------------------------------------------------------------------------------
/src/assets/brand/icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/brand/icon_512.png
--------------------------------------------------------------------------------
/src/assets/brand/icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/brand/icon_64.png
--------------------------------------------------------------------------------
/src/assets/brand/macos_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/brand/macos_512.png
--------------------------------------------------------------------------------
/src/assets/brand/maskable_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/brand/maskable_512.png
--------------------------------------------------------------------------------
/src/assets/brand/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/brand/og.png
--------------------------------------------------------------------------------
/src/assets/icons/add.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/add_active.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/alfred.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/icons/arrow_alt.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/icons/audio.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/blank.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/cache_failed.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/cloud_active.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/document.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/edit_active.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/gdrive.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/gdrive_active.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/inbox_active.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/info.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/markdown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/menu.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_arrow.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_article.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_audio.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_broken.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_cache_failed.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_comment.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_document.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_duplicate.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_expand.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_next.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_reminder.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_search.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/micro_video.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/more_horizontal.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/night.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/show_active.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/sidebar.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/icons/sort_-title.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/sort_-title_active.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/sort_rating.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/type.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/upload_active.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/view_simple.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/assets/icons/youtube.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/languages/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "de": "Deutsch",
3 | "en": "English",
4 | "es": "Español",
5 | "fr": "Français",
6 | "hi": "हिन्दी",
7 | "it": "Italiano",
8 | "ja": "日本語",
9 | "ko": "한국어",
10 | "nl": "Nederlands",
11 | "pl": "Polski",
12 | "pt_BR": "Português (Brasil)",
13 | "ru": "Русский",
14 | "sv": "Svenska",
15 | "tr": "Türkçe",
16 | "zh-Hans": "中文(汉语)",
17 | "zh-Hant": "中文(漢語)"
18 | }
--------------------------------------------------------------------------------
/src/assets/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
--------------------------------------------------------------------------------
/src/assets/sw.js:
--------------------------------------------------------------------------------
1 | // On install, cache some stuff
2 | addEventListener('install', function (event) {
3 |
4 | });
5 |
6 |
7 | // listen for requests
8 | addEventListener('fetch', function (event) {
9 |
10 | });
--------------------------------------------------------------------------------
/src/assets/target/extension/action_chrome_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_chrome_16.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_chrome_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_chrome_24.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_chrome_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_chrome_32.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_firefox_dark_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_firefox_dark_16.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_firefox_dark_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_firefox_dark_24.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_firefox_dark_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_firefox_dark_32.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_firefox_light_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_firefox_light_16.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_firefox_light_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_firefox_light_24.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_firefox_light_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_firefox_light_32.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_safari.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_safari.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_safari_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_safari_16.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_safari_19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_safari_19.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_safari_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_safari_32.png
--------------------------------------------------------------------------------
/src/assets/target/extension/action_safari_38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/action_safari_38.png
--------------------------------------------------------------------------------
/src/assets/target/extension/welcome/logo-icon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/welcome/logo-icon.webp
--------------------------------------------------------------------------------
/src/assets/target/extension/welcome/screen-highlights.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/welcome/screen-highlights.webp
--------------------------------------------------------------------------------
/src/assets/target/extension/welcome/screen-save.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/welcome/screen-save.webp
--------------------------------------------------------------------------------
/src/assets/target/extension/welcome/screen-search.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/welcome/screen-search.webp
--------------------------------------------------------------------------------
/src/assets/target/extension/welcome/screen-sidepanel.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raindropio/app/ac9acd4ba5eeb70f6af600b722a01b8c4f39cd92/src/assets/target/extension/welcome/screen-sidepanel.webp
--------------------------------------------------------------------------------
/src/co/bookmarks/add/extension/highlights/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import t from '~t'
3 | import { Link } from 'react-router-dom'
4 | import { MenuItem } from '~co/overlay/popover'
5 | import Icon from '~co/common/icon'
6 |
7 | export default function BookmarksAddExtensionHighlights() {
8 | return (
9 |
15 | )
16 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/add/extension/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { PropTypes } from 'prop-types'
3 |
4 | import { ButtonsGroup } from '~co/common/button'
5 | import Tab from './tab'
6 | import More from './more'
7 |
8 | function BookmarksAdd(props) {
9 | return (
10 |
11 |
12 |
13 |
14 | )
15 | }
16 |
17 | BookmarksAdd.propTypes = {
18 | spaceId: PropTypes.any,
19 | onEdit: PropTypes.func
20 | }
21 |
22 | export default BookmarksAdd
--------------------------------------------------------------------------------
/src/co/bookmarks/add/extension/more.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { PropTypes } from 'prop-types'
3 |
4 | import { More, Menu } from '~co/overlay/popover'
5 | import Tabs from './tabs'
6 | import File from '../fallback/file'
7 | import Highlights from './highlights'
8 |
9 | function BookmarksAddMore(props) {
10 | return (
11 |
12 |
17 |
18 | )
19 | }
20 |
21 | BookmarksAddMore.propTypes = {
22 | spaceId: PropTypes.any,
23 | onEdit: PropTypes.func
24 | }
25 |
26 | export default BookmarksAddMore
--------------------------------------------------------------------------------
/src/co/bookmarks/add/extension/tab/button.module.styl:
--------------------------------------------------------------------------------
1 | .button[data-init='true'] {
2 | pointer-events: none
3 | }
4 |
5 | .label {
6 | overflow: visible
7 | }
8 |
9 | html:global(.mobile) .label {
10 | display: none
11 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/add/extension/tabs/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import t from '~t'
3 | import { Link } from 'react-router-dom'
4 | import { MenuItem } from '~co/overlay/popover'
5 | import Icon from '~co/common/icon'
6 |
7 | export default function BookmarksAddExtensionTabs({ spaceId }) {
8 | return (
9 |
15 | )
16 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/add/fallback/link.module.css:
--------------------------------------------------------------------------------
1 | .modal {
2 | width: 11rem
3 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/add/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { target } from '~target'
3 |
4 | let Component = target == 'extension' ?
5 | require('./extension').default :
6 | require('./fallback').default
7 |
8 | export default class BookmarksAddWrap extends React.PureComponent {
9 | static defaultProps = {
10 | spaceId: 0,
11 | onEdit: undefined //func([item])
12 | }
13 |
14 | render() {
15 | return (
16 |
17 | )
18 | }
19 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/container/scroll.module.styl:
--------------------------------------------------------------------------------
1 | .scroll {
2 | overflow-x: clip
3 | overflow-y: auto
4 | flex: 1
5 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/container/wrap.js:
--------------------------------------------------------------------------------
1 | import s from './wrap.module.styl'
2 | import React from 'react'
3 |
4 | import AccentColor from '~co/collections/item/accentColor'
5 | import Drop from '../dnd/drop'
6 |
7 | export default function BookmarksContainerWrap({ spaceId, children }) {
8 | return (
9 | {style=>
10 | {({ dropHandlers, isDropping })=>
11 |
15 | {children}
16 |
17 | }
18 | }
19 | )
20 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/container/wrap.module.styl:
--------------------------------------------------------------------------------
1 | .wrap {
2 | width: 100%
3 | height: 100%
4 | position: relative
5 | display: flex
6 | flex-direction: column
7 | }
8 |
9 | .isDropping:before {
10 | background: unquote('hsla(var(--accent-hsl), .1)')
11 | left:0;top:0;right:0;bottom:0;
12 | content: ""
13 | position: absolute
14 | z-index:11
15 | pointer-events: none
16 | box-shadow: inset 0px 0px 0px 2px var(--accent-color)
17 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/edit/form/action/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Buttons } from '~co/common/form'
3 | import Main from './main'
4 |
5 | export default function BookmarkEditAction({ buttons, ...etc }) {
6 | return (
7 |
8 | {buttons}
9 |
10 |
11 | )
12 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/edit/form/date.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import t from '~t'
3 |
4 | import { SubLabel } from '~co/common/form'
5 | import { ShortDateTime } from '~modules/format/date'
6 |
7 | export default function BookmarkEditFormDate({ item: { created } }) {
8 | if (!created)
9 | return null
10 |
11 | return (
12 | <>
13 |
14 | {t.s('saved')}
15 | >
16 | )
17 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/edit/form/important.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from 'react'
2 | import t from '~t'
3 | import Button from '~co/common/button'
4 | import Icon from '~co/common/icon'
5 |
6 | export default function BookmarkEditFormImportant({ item: { important }, onChange, onSave }) {
7 | const onToggle = useCallback(e=>{
8 | e.preventDefault()
9 | onChange({ important: !important })
10 | onSave()
11 | }, [important])
12 |
13 | return (
14 |
20 | )
21 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/edit/form/index.module.styl:
--------------------------------------------------------------------------------
1 | .edit {
2 | width: 100%
3 | max-width: 13rem //520px
4 | margin: 0 auto
5 | padding: var(--padding-small) 0
6 | display: flex
7 | }
8 |
9 | .form {
10 | flex: 1
11 | position: relative
12 |
13 | &[data-status='loading'],
14 | &[data-status='idle'] {
15 | pointer-events: none
16 | user-select: none
17 | }
18 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/edit/form/note.module.styl:
--------------------------------------------------------------------------------
1 | .note {
2 | position: relative
3 | }
4 |
5 | .button {
6 | position: absolute
7 | right: var(--padding-micro)
8 | bottom: var(--padding-micro)
9 | --primary-text-color: var(--disable-text-color)
10 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/edit/index.module.styl:
--------------------------------------------------------------------------------
1 | .edit {
2 | width: 100%
3 | max-width: 12.5rem //500px
4 | margin: 0 auto
5 | display: flex
6 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/footer/index.module.styl:
--------------------------------------------------------------------------------
1 | .footer {
2 | width: 100%
3 | height: var(--header-height)
4 | display: flex
5 | align-items: center
6 | justify-content: center
7 | padding: 0 var(--padding-medium)
8 | font-size: var(--secondary-font-size)
9 | color: var(--secondary-text-color)
10 |
11 | &[data-compact='true'] {
12 | background: unquote('linear-gradient(to bottom, hsla(var(--background-hsl), 0) 0, var(--background-color) 100%)')
13 | }
14 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/header/index.js:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { makeSelectModeEnabled } from '~data/selectors/bookmarks'
4 |
5 | import Regular from './regular'
6 | import SelectMode from './selectMode'
7 |
8 | export default function BookmarksHeader(props) {
9 | const { spaceId } = props
10 |
11 | const getSelectModeEnabled = useRef(makeSelectModeEnabled()).current
12 | const selectModeEnabled = useSelector(state=>getSelectModeEnabled(state, spaceId))
13 |
14 | return selectModeEnabled ?
15 | :
16 |
17 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/header/regular/icon.module.styl:
--------------------------------------------------------------------------------
1 | .button {
2 | position: relative
3 |
4 | &[data-selectable='false'] {
5 | pointer-events: none
6 | }
7 | }
8 |
9 | .icon, .select {
10 | transition: .2s ease-in-out
11 | transition-delay: .2s
12 | }
13 |
14 | .select {
15 | opacity: 0
16 | position: absolute
17 | left: 0
18 | right: 0
19 | bottom: 0
20 | top: 0
21 | pointer-events: none
22 | display: flex
23 | align-items: center
24 | justify-content: center
25 | }
26 |
27 | [data-is-header]:hover .button[data-selectable='true'],
28 | .button[data-selectable='true']:focus-visible {
29 | .select {
30 | opacity: 1
31 | }
32 |
33 | .icon {
34 | opacity: 0
35 | }
36 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/header/regular/index.module.styl:
--------------------------------------------------------------------------------
1 | .header:hover .rename, .rename:focus-visible,
2 | .header:hover .open, .open:focus-visible {
3 | opacity: 1
4 | }
5 |
6 | .rename, .open {
7 | opacity: 0
8 | transition: .2s ease-in-out opacity;
9 | transition-delay: .1s
10 |
11 | &:focus-visible {
12 | transition: none
13 | }
14 | }
15 |
16 | html:global(:not(.extension)) .open {
17 | display: none
18 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/header/regular/open.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import t from '~t'
3 | import config from '~config'
4 | import Button from '~co/common/button'
5 | import Icon from '~co/common/icon'
6 |
7 | export default ({ className, spaceId })=>(
8 |
15 | )
--------------------------------------------------------------------------------
/src/co/bookmarks/header/selectMode/checkbox.module.styl:
--------------------------------------------------------------------------------
1 | .check {
2 | width: var(--icon-size)
3 | height: var(--icon-size)
4 | display: flex
5 | align-items: center
6 | justify-content: center
7 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/index.module.styl:
--------------------------------------------------------------------------------
1 | .bookmarks {
2 | --lazy-item-height: var(--header-height)
3 | overflow-x: clip
4 | overflow-y: auto
5 | height: 100%
6 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/item/actions.module.styl:
--------------------------------------------------------------------------------
1 | //Actions
2 | .actions {
3 | position: absolute
4 | z-index: 1
5 | right: var(--padding-medium)
6 | top: var(--padding-medium)
7 | display: inline-grid
8 | grid-auto-flow: column
9 | grid-gap: var(--padding-small)
10 | justify-content: start
11 | display: none
12 | //transition: opacity .1s linear
13 |
14 | > * {
15 | display: none
16 | }
17 | }
18 |
19 | for button in current_tab new_tab preview web copy important tags edit remove
20 | :global(.button-{button}) [data-button={button}] {
21 | display: grid
22 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/item/cover/index.js:
--------------------------------------------------------------------------------
1 | import View from './view'
2 |
3 | export default View
--------------------------------------------------------------------------------
/src/co/bookmarks/item/cover/size.js:
--------------------------------------------------------------------------------
1 | export default function(view, amplifier=1) {
2 | switch(view){
3 | case 'simple':
4 | return {
5 | width: 20,
6 | height: 20,
7 | ar: '1:1'
8 | }
9 |
10 | case 'grid':
11 | case 'masonry':
12 | return {
13 | width: 194 + (amplifier * 30),
14 | ar: view == 'grid' ? '16:9' : undefined
15 | }
16 |
17 | default:
18 | return {
19 | width: 56,
20 | height: 48,
21 | ar: '7:6'
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/item/highlights.module.styl:
--------------------------------------------------------------------------------
1 | .highlight {
2 | margin: var(--padding-mini) 0
3 |
4 | display: -webkit-box
5 | line-clamp: 2
6 | -webkit-line-clamp: 2
7 | -webkit-box-orient: vertical
8 | overflow: hidden
9 | overflow: clip
10 |
11 | .note {
12 | margin-right: var(--padding-small)
13 | }
14 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/item/info/path.module.styl:
--------------------------------------------------------------------------------
1 | .path {
2 | display: inline-flex
3 | align-items: center
4 | justify-content: center
5 | font-weight: 500
6 | position: relative
7 | z-index: 1
8 |
9 | &:hover {
10 | text-decoration: underline
11 | }
12 | }
13 |
14 | a.path,
15 | .path {
16 | color: inherit
17 | }
18 |
19 | .icon {
20 | width: var(--padding-medium)
21 | height: var(--padding-medium)
22 | margin-right: var(--padding-small)
23 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/item/reminder.js:
--------------------------------------------------------------------------------
1 | import s from './reminder.module.styl'
2 | import React from 'react'
3 | import { ShortDateTime } from '~modules/format/date'
4 | import Icon from '~co/common/icon'
5 |
6 | export default function BookmarksItemReminder({ reminder }) {
7 | if (reminder?.date)
8 | return (
9 |
10 |
11 | {' '}
12 |
13 |
14 | )
15 |
16 | return null
17 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/item/reminder.module.styl:
--------------------------------------------------------------------------------
1 | .reminder {
2 | font-size: var(--secondary-font-size)
3 | color: var(--reminder-color)
4 | padding: var(--padding-mini) 0
5 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/item/tags.module.styl:
--------------------------------------------------------------------------------
1 | :global(.hide-tags) .tags {
2 | display: none
3 | }
4 |
5 | .tags {
6 | a {
7 | color: var(--accent-color)
8 | margin-right: var(--padding-small)
9 | }
10 |
11 | &:empty {
12 | display:none
13 | }
14 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/items/empty/view.module.styl:
--------------------------------------------------------------------------------
1 | .empty {
2 | flex: 1
3 | display: flex
4 | align-items: center
5 | justify-content: center
6 | text-align: center
7 | padding: var(--padding-large)
8 |
9 | &:empty {
10 | //otherwise virtualized list glitches:
11 | padding: 0
12 | min-height: 1px
13 | }
14 |
15 | svg path {
16 | fill: var(--accent-color)
17 | }
18 |
19 | svg :not([fill]) {
20 | fill: currentColor
21 | }
22 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/items/sortable/index.module.styl:
--------------------------------------------------------------------------------
1 | .ghost {
2 | opacity: 0
3 | transform: scale(0.9)
4 | transition-duration: 0s !important
5 | content-visibility: visible !important
6 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/items/view/grid.js:
--------------------------------------------------------------------------------
1 | import s from './grid.module.styl'
2 | import React, { useMemo } from 'react'
3 | import { useSelector } from 'react-redux'
4 | import { getCoverSize } from '~data/selectors/bookmarks'
5 | import itemCoverSize from '~co/bookmarks/item/cover/size'
6 |
7 | export default function BookmarksItemsGrid({ className='', children }) {
8 | const coverSize = useSelector(state=>getCoverSize(state, 'grid'))
9 | const style = useMemo(()=>({
10 | '--grid-item-width': itemCoverSize('grid', coverSize).width+'px'
11 | }), [coverSize])
12 |
13 | return (
14 |
17 | {children}
18 |
19 | )
20 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/items/view/grid.module.styl:
--------------------------------------------------------------------------------
1 | .grid {
2 | --lazy-item-height: 300px
3 |
4 | main {
5 | display: grid;
6 | grid-template-columns: unquote('repeat(auto-fill, minmax(min(calc(50% - var(--padding-medium) * 2), var(--grid-item-width)), 1fr))')
7 | grid-template-rows: 1fr;
8 | grid-gap: var(--padding-medium)
9 | padding: var(--padding-medium)
10 | padding-bottom: 0
11 | }
12 |
13 | [data-lazy-item] {
14 | transition: background .2s ease-in-out;
15 |
16 | &:empty {
17 | box-shadow: inset 0 0 0 var(--shadow-height) var(--shadow-color)
18 | border-radius: var(--border-radius)
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/items/view/list.js:
--------------------------------------------------------------------------------
1 | import s from './list.module.styl'
2 | import React from 'react'
3 | import { useSelector } from 'react-redux'
4 |
5 | export default function BookmarksItemsList({ className='', children }) {
6 | const listCoverRight = useSelector(state=>state.config.raindrops_list_cover_right)
7 |
8 | return (
9 |
10 | {children}
11 |
12 | )
13 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/items/view/list.module.styl:
--------------------------------------------------------------------------------
1 | .list {
2 | --lazy-item-height: 100px
3 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/items/view/masonry.module.styl:
--------------------------------------------------------------------------------
1 | .masonry {
2 | main {
3 | display: grid;
4 | grid-template-columns: unquote('repeat(auto-fill, minmax(min(calc(50% - var(--padding-medium) * 2), var(--grid-item-width)), 1fr))')
5 | grid-auto-rows: 15px //this value + grid-gap should be send to Lazy component in `gridCellSize`, also smaller than 20 value works unpredictable :(
6 | grid-gap: var(--padding-medium)
7 | padding: var(--padding-medium)
8 | padding-bottom: 0
9 | }
10 |
11 | [data-lazy-item] {
12 | grid-row-end: span 5 //default height (calculated as =(grid-auto-rows+grid-gap)*N )
13 | }
14 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/items/view/simple.js:
--------------------------------------------------------------------------------
1 | import s from './simple.module.styl'
2 | import React from 'react'
3 |
4 | export default function BookmarksItemsSimple({ className='', children }) {
5 | return (
6 |
7 | {children}
8 |
9 | )
10 | }
--------------------------------------------------------------------------------
/src/co/bookmarks/items/view/simple.module.styl:
--------------------------------------------------------------------------------
1 | .simple {
2 | --lazy-item-height: 75px
3 | }
--------------------------------------------------------------------------------
/src/co/collections/group/view.module.styl:
--------------------------------------------------------------------------------
1 | .section {
2 | cursor: pointer
3 | }
--------------------------------------------------------------------------------
/src/co/collections/item/title.module.styl:
--------------------------------------------------------------------------------
1 | .path {
2 | direction: rtl
3 | text-align: left
4 | display: block
5 | white-space: nowrap
6 | overflow: hidden
7 | text-overflow: ellipsis
8 | word-wrap: break-word
9 | }
10 |
11 | .parents {
12 | color: var(--secondary-text-color)
13 | }
--------------------------------------------------------------------------------
/src/co/collections/items/empty.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Item } from '~co/common/list'
3 |
4 | export default function CollectionsItemsEmpty() {
5 | return (
6 |
7 | -
8 | —————————————
9 |
10 | -
11 | —————————
12 |
13 | -
14 | —————————
15 |
16 |
17 | )
18 | }
--------------------------------------------------------------------------------
/src/co/collections/items/index.module.styl:
--------------------------------------------------------------------------------
1 | .wrap {
2 | position: relative
3 | height: 100%
4 | }
--------------------------------------------------------------------------------
/src/co/collections/items/tree.module.styl:
--------------------------------------------------------------------------------
1 | .tree {
2 | overflow-x: clip !important
3 | overflow-y: overlay !important
4 | }
--------------------------------------------------------------------------------
/src/co/collections/picker/index.module.styl:
--------------------------------------------------------------------------------
1 | .content {
2 | overflow: hidden
3 | overflow: clip
4 | }
--------------------------------------------------------------------------------
/src/co/collections/sharing/index.module.styl:
--------------------------------------------------------------------------------
1 | .sharing {
2 | width: 13rem //520px
3 | }
--------------------------------------------------------------------------------
/src/co/collections/sharing/title/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 | import { makeCollection } from '~data/selectors/collections'
4 | import View from './view'
5 |
6 | class CollectionSharingTitle extends React.Component {
7 | render() {
8 | return
10 | }
11 | }
12 |
13 | export default connect(
14 | () => {
15 | const getCollection = makeCollection()
16 |
17 | return (state, { _id })=>
18 | getCollection(state, _id)
19 | }
20 | )(CollectionSharingTitle)
--------------------------------------------------------------------------------
/src/co/collections/sharing/title/view.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import t from '~t'
3 | import { Header } from '~co/overlay/modal'
4 |
5 | export default function CollectionSharingTitleView({ title }) {
6 | return (
7 |
10 | )
11 | }
--------------------------------------------------------------------------------
/src/co/common/alert/index.js:
--------------------------------------------------------------------------------
1 | import s from './index.module.styl'
2 | import React from 'react'
3 |
4 | export default function Alert({ className='', variant='default', children, ...etc }) {
5 | let icon = ''
6 | switch (variant) {
7 | case 'warning': icon = '⚠️'; break
8 | }
9 |
10 | return (
11 |
14 | {icon && {icon} }
15 | {children}
16 |
17 | )
18 | }
--------------------------------------------------------------------------------
/src/co/common/footer/index.js:
--------------------------------------------------------------------------------
1 | import s from './index.module.styl'
2 | import React from 'react'
3 |
4 | function Footer({ as='div', className='', forwardedRef, ...etc }) {
5 | const Component = as
6 |
7 | return (
8 |
12 | )
13 | }
14 |
15 | export default React.forwardRef((props, ref) => {
16 | return
17 | })
18 |
19 | export function Space() {
20 | return
21 | }
--------------------------------------------------------------------------------
/src/co/common/form/checkbox.js:
--------------------------------------------------------------------------------
1 | import s from './checkbox.module.styl'
2 | import React from 'react'
3 |
4 | export function Checkbox({ className='', children, hidden, active, ...etc }) {
5 | return (
6 |
10 | )
11 | }
--------------------------------------------------------------------------------
/src/co/common/form/checkbox.module.styl:
--------------------------------------------------------------------------------
1 | .wrap {
2 | display: grid
3 | grid-auto-flow: column
4 | grid-gap: var(--padding-small)
5 | align-items: center
6 | justify-content: start
7 | min-height: var(--button-height)
8 | }
--------------------------------------------------------------------------------
/src/co/common/form/group.js:
--------------------------------------------------------------------------------
1 | import s from './group.module.styl'
2 | import React from 'react'
3 |
4 | export function Group({ className='', ...etc }) {
5 | return
6 | }
--------------------------------------------------------------------------------
/src/co/common/form/group.module.styl:
--------------------------------------------------------------------------------
1 | .group {
2 | display: flex
3 | flex-direction: row
4 | align-items: center
5 | flex-wrap: wrap
6 | gap: var(--padding-small)
7 | }
--------------------------------------------------------------------------------
/src/co/common/form/index.js:
--------------------------------------------------------------------------------
1 | export * from './title'
2 | export * from './label'
3 | export * from './sub_label'
4 | export * from './text'
5 | export * from './layout'
6 | export * from './checkbox'
7 | export * from './radio'
8 | export * from './range'
9 | export * from './search'
10 | export * from './separator'
11 | export * from './progress'
12 | export * from './toggle'
13 | export * from './date_time'
14 | export * from './group'
--------------------------------------------------------------------------------
/src/co/common/form/label.js:
--------------------------------------------------------------------------------
1 | import s from './label.module.styl'
2 | import React from 'react'
3 |
4 | export function Label({ className='', ...etc }) {
5 | return
6 | }
--------------------------------------------------------------------------------
/src/co/common/form/label.module.styl:
--------------------------------------------------------------------------------
1 | .label {
2 | font-size: var(--secondary-font-size)
3 | color: var(--secondary-text-color)
4 | min-height: var(--button-height)
5 | display: flex
6 | align-items: center
7 | justify-content: space-between
8 |
9 | a {
10 | font-weight: 600
11 | }
12 | }
--------------------------------------------------------------------------------
/src/co/common/form/layout.js:
--------------------------------------------------------------------------------
1 | import s from './layout.module.styl'
2 | import React from 'react'
3 |
4 | export function Layout({ className='', type, as='div', ...etc }) {
5 | const Component = as
6 | return
7 | }
8 |
9 | export function Buttons({ className='', variant, ...etc }) {
10 | return
11 | }
--------------------------------------------------------------------------------
/src/co/common/form/radio.js:
--------------------------------------------------------------------------------
1 | import s from './radio.module.styl'
2 | import React from 'react'
3 |
4 | export function Radio({ className='', style, children, ...etc }) {
5 | return (
6 |
10 | )
11 | }
--------------------------------------------------------------------------------
/src/co/common/form/radio.module.styl:
--------------------------------------------------------------------------------
1 | .wrap {
2 | display: grid
3 | grid-auto-flow: column
4 | grid-gap: var(--padding-small)
5 | align-items: center
6 | justify-content: start
7 | height: var(--button-height)
8 |
9 | &[data-disabled='true'] {
10 | color: var(--disable-text-color)
11 | }
12 | }
--------------------------------------------------------------------------------
/src/co/common/form/range.js:
--------------------------------------------------------------------------------
1 | import s from './range.module.styl'
2 | import React from 'react'
3 |
4 | export function Range({ className='', ...etc }) {
5 | return (
6 |
9 | )
10 | }
--------------------------------------------------------------------------------
/src/co/common/form/range.module.styl:
--------------------------------------------------------------------------------
1 | .wrap {
2 | display: grid
3 | grid-auto-flow: column
4 | grid-gap: var(--padding-small)
5 | align-items: center
6 | justify-content: start
7 | height: var(--button-height)
8 | }
--------------------------------------------------------------------------------
/src/co/common/form/search.module.styl:
--------------------------------------------------------------------------------
1 | .input {
2 | padding-right: 0
3 | }
--------------------------------------------------------------------------------
/src/co/common/form/separator.js:
--------------------------------------------------------------------------------
1 | import s from './separator.module.styl'
2 | import React from 'react'
3 |
4 | export function Separator({ className='', variant, ...etc }) {
5 | return (
6 |
10 | )
11 | }
--------------------------------------------------------------------------------
/src/co/common/form/separator.module.styl:
--------------------------------------------------------------------------------
1 | .separator {
2 | grid-column: 1 / -1
3 | height: 1px
4 |
5 | &[data-variant='default'] {
6 | box-shadow: inset 0 var(--shadow-height) 0 var(--shadow-color)
7 | }
8 |
9 | &[data-variant='transparent'] {
10 | }
11 | }
12 |
13 | .separator + .separator {
14 | display: none
15 | }
--------------------------------------------------------------------------------
/src/co/common/form/sub_label.js:
--------------------------------------------------------------------------------
1 | import s from './sub_label.module.styl'
2 | import React from 'react'
3 |
4 | export function SubLabel({ className='', ...etc }) {
5 | return
6 | }
--------------------------------------------------------------------------------
/src/co/common/form/sub_label.module.styl:
--------------------------------------------------------------------------------
1 | .subLabel {
2 | font-size: var(--secondary-font-size)
3 | color: var(--secondary-text-color)
4 |
5 | &:not(:last-child) {
6 | margin-bottom: var(--padding-small)
7 | }
8 | }
--------------------------------------------------------------------------------
/src/co/common/form/title.js:
--------------------------------------------------------------------------------
1 | import s from './title.module.styl'
2 | import React from 'react'
3 |
4 | export function Title({ className='', ...etc }) {
5 | return
6 | }
--------------------------------------------------------------------------------
/src/co/common/form/title.module.styl:
--------------------------------------------------------------------------------
1 | .title {
2 | font-size: var(--title-font-size)
3 | font-weight: 600
4 | color: var(--title-text-color)
5 | min-height: var(--button-height)
6 | display: flex
7 | align-items: center
8 | grid-column: 1 / -1
9 | }
--------------------------------------------------------------------------------
/src/co/common/form/toggle.js:
--------------------------------------------------------------------------------
1 | import s from './toggle.module.styl'
2 | import React, { useCallback } from 'react'
3 |
4 | export function Toggle({ className='', hidden, value, onChange, ...etc }) {
5 | const onClick = useCallback(e=>{
6 | e.preventDefault()
7 | onChange(!value)
8 | }, [value])
9 |
10 | return (
11 |
12 |
13 |
14 | )
15 | }
--------------------------------------------------------------------------------
/src/co/common/list/index.js:
--------------------------------------------------------------------------------
1 | export * from './section'
2 | export * from './item'
3 | export * from './wrap'
4 | export * from './separator'
--------------------------------------------------------------------------------
/src/co/common/list/section.js:
--------------------------------------------------------------------------------
1 | import s from './section.module.styl'
2 | import React from 'react'
3 |
4 | export function Section({ className='', active, isDragging, isDropping, ...etc }) {
5 | return (
6 |
9 | )
10 | }
11 |
12 | export function SectionTitle({ className='', ...etc }) {
13 | return (
14 |
15 | )
16 | }
17 |
18 | export function SectionActions({ className='', ...etc }) {
19 | return (
20 |
21 | )
22 | }
--------------------------------------------------------------------------------
/src/co/common/list/separator.js:
--------------------------------------------------------------------------------
1 | import s from './separator.module.styl'
2 | import React from 'react'
3 |
4 | export function Separator(){
5 | return
6 | }
--------------------------------------------------------------------------------
/src/co/common/list/separator.module.styl:
--------------------------------------------------------------------------------
1 | .separator {
2 | box-shadow: inset 0 var(--shadow-height) 0 var(--shadow-color)
3 | height: 1px
4 | display: block;
5 | margin: 5px 0px;
6 | }
--------------------------------------------------------------------------------
/src/co/common/list/wrap.js:
--------------------------------------------------------------------------------
1 | import s from './wrap.module.styl'
2 | import React from 'react'
3 |
4 | export function Wrap({ as='div', className='', ...etc }) {
5 | const Component = as
6 |
7 | return (
8 |
11 | )
12 | }
--------------------------------------------------------------------------------
/src/co/common/list/wrap.module.styl:
--------------------------------------------------------------------------------
1 | .wrap {
2 | overflow-x: hidden
3 | border-radius: var(--border-radius)
4 | box-shadow: inset 0 0 0 var(--shadow-height) var(--shadow-color)
5 | background: var(--background-color)
6 |
7 | > :not(:last-child) {
8 | box-shadow: 0 var(--shadow-height) 0 var(--shadow-color)
9 | }
10 | }
--------------------------------------------------------------------------------
/src/co/common/safeHtml.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react'
2 | import DOMPurify from 'dompurify'
3 |
4 | function safe(html) {
5 | return {
6 | dangerouslySetInnerHTML: {
7 | __html: DOMPurify.sanitize(html, { ALLOWED_TAGS: ['em'], ALLOWED_ATTR: [] })
8 | }
9 | }
10 | }
11 |
12 | export default memo(
13 | function({ html='', tagName='div', ...etc }) {
14 | const Tag = tagName
15 |
16 | if (html.includes('<'))
17 | return (
18 |
21 | )
22 |
23 | return {html}
24 | }
25 | )
--------------------------------------------------------------------------------
/src/co/common/select/index.js:
--------------------------------------------------------------------------------
1 | import Single from './single'
2 | export * from './multi'
3 |
4 | export default Single
--------------------------------------------------------------------------------
/src/co/common/slider/index.module.styl:
--------------------------------------------------------------------------------
1 | .slider {
2 | display: flex;
3 | align-items: center;
4 | color: var(--secondary-text-color);
5 | padding: 12px;
6 |
7 | a {
8 | color: currentColor;
9 |
10 | &:first-child {
11 | margin-right: 10px;
12 | }
13 |
14 | &:last-child {
15 | margin-left: 10px;
16 | }
17 |
18 | &:active {
19 | color: var(--primary-text-color);
20 | }
21 | }
22 |
23 | input {
24 | flex:1;
25 | }
26 | }
--------------------------------------------------------------------------------
/src/co/common/superImg.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function superImg({src,className='',width,height,style={}}){
4 | var retina = src, indexOfExt = src.lastIndexOf('.');
5 | retina = retina.substr(0, indexOfExt) + '@2x' + retina.substr(indexOfExt);
6 | retina = require('~assets/images/'+retina).default;
7 |
8 | var nonRetina = require('~assets/images/'+src).default;
9 |
10 | //src={nonRetina}
11 | return (
12 |
13 | );
14 | }
--------------------------------------------------------------------------------
/src/co/common/superLink/index.module.styl:
--------------------------------------------------------------------------------
1 | .superLink {
2 | position: absolute
3 | left: 0
4 | right: 0
5 | bottom: 0
6 | top: 0
7 |
8 | color: transparent
9 | font-size: 0
10 |
11 | * {
12 | display: none
13 | }
14 |
15 | &:not(:focus-visible) {
16 | outline: none
17 | }
18 | }
--------------------------------------------------------------------------------
/src/co/common/tabs/index.module.styl:
--------------------------------------------------------------------------------
1 | .tabs {
2 | display: inline-flex
3 |
4 | > :not(:last-child) {
5 | margin-right: var(--padding-small)
6 | }
7 | }
8 |
9 | .item {
10 | position: relative
11 | }
--------------------------------------------------------------------------------
/src/co/common/webview/browser.module.styl:
--------------------------------------------------------------------------------
1 | .iframe {
2 | display: block
3 | }
--------------------------------------------------------------------------------
/src/co/common/webview/electron.module.styl:
--------------------------------------------------------------------------------
1 | .webview {
2 | &[data-started='false'] {
3 | display: none !important
4 | }
5 | }
--------------------------------------------------------------------------------
/src/co/common/webview/error.module.styl:
--------------------------------------------------------------------------------
1 | .wrap {
2 | position: absolute
3 | left: 0;right: 0;bottom: 0;top: 0;
4 | display: flex
5 | flex-direction: column
6 | align-items: center
7 | justify-content: center
8 | }
9 |
10 | .screenshot, .message {
11 | padding: var(--padding-large)
12 | }
13 |
14 | .message {
15 | text-align: center
16 | }
17 |
18 | .screenshot {
19 | img {
20 | border-radius: var(--border-radius);
21 | display: block;
22 | max-width: 100%;
23 | height: auto;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/co/common/webview/preloader.js:
--------------------------------------------------------------------------------
1 | import s from './preloader.module.styl'
2 | import React from 'react'
3 |
4 | export default function WebViewPreloader({ className='' }) {
5 | return (
6 |
9 | )
10 | }
--------------------------------------------------------------------------------
/src/co/common/webview/preloader.module.styl:
--------------------------------------------------------------------------------
1 | .wrap {
2 | position: absolute
3 | left: 0;right: 0;top: 0;
4 | height: 3px
5 | }
6 |
7 | .line {
8 | background: var(--accent-color)
9 | width: 100%
10 | height: 100%
11 | border-top-right-radius: 3px
12 | border-bottom-right-radius: 3px
13 | animation: lineDraw 5s ease-in-out
14 | transform-origin: top left
15 | }
16 |
17 | @keyframes lineDraw {
18 | 0% {
19 | transform: scaleX(0)
20 | }
21 | 20% {
22 | transform: scaleX(0.5)
23 | }
24 | 100% {
25 | transform: scaleX(1)
26 | }
27 | }
--------------------------------------------------------------------------------
/src/co/filters/section/contextmenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import t from '~t'
3 | import Popover, { Menu, MenuItem } from '~co/overlay/popover'
4 |
5 | export default function CollectionsItemContextmenu({
6 | item: { hidden },
7 | onContextMenuClose, onClick
8 | }) {
9 | return (
10 |
11 |
16 |
17 | )
18 | }
--------------------------------------------------------------------------------
/src/co/filters/section/view.module.styl:
--------------------------------------------------------------------------------
1 | .section {
2 | cursor: pointer
3 | }
--------------------------------------------------------------------------------
/src/co/highlights/items/index.module.styl:
--------------------------------------------------------------------------------
1 | .highlights {
2 | border-radius: var(--border-radius)
3 | overflow: hidden
4 | box-shadow: 0 0 0 var(--shadow-height) var(--shadow-color)
5 |
6 | & > :not(:first-child) {
7 | box-shadow: 0 calc(var(--shadow-height) * -1) 0 var(--shadow-color)
8 | }
9 | }
--------------------------------------------------------------------------------
/src/co/highlights/items/useScrollToNew.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react'
2 |
3 | export default function useScrollToNew(containerRef, highlights) {
4 | const prevCount = useRef(0)
5 |
6 | useEffect(()=>{
7 | if (!containerRef.current) return
8 | if (highlights.length && prevCount.current && highlights.length > prevCount.current)
9 | containerRef.current.children[containerRef.current.children.length - 1].scrollIntoView()
10 | }, [highlights.length, containerRef])
11 |
12 | useEffect(() => prevCount.current = highlights.length, [highlights.length])
13 | }
--------------------------------------------------------------------------------
/src/co/highlights/text/index.js:
--------------------------------------------------------------------------------
1 | import s from './index.module.styl'
2 | import React from 'react'
3 |
4 | export default function HighlightsText({ className='', color, ...etc }) {
5 | return (
6 |
10 | )
11 | }
--------------------------------------------------------------------------------
/src/co/highlights/text/index.module.styl:
--------------------------------------------------------------------------------
1 | .text {
2 | padding-left: calc(var(--padding-mini) + var(--padding-small))
3 | position: relative
4 |
5 | white-space: pre
6 | white-space: pre-wrap
7 |
8 | &:before {
9 | content: ''
10 | position: absolute
11 | left: 0;top:3px;bottom:3px
12 | width: 3px
13 | border-radius: 3px
14 | background: var(--highlight-color, #ffee00)
15 | background-image: linear-gradient(to bottom, rgba(255,255,255,.3) 0, rgba(255,255,255,.3) 100%)
16 | }
17 | }
--------------------------------------------------------------------------------
/src/co/overlay/dialog/view/alert.module.styl:
--------------------------------------------------------------------------------
1 | .alert {
2 | width: 8rem //320px
3 | }
4 |
5 | .description {
6 | color: var(--primary-text-color)
7 | }
--------------------------------------------------------------------------------
/src/co/overlay/dialog/view/confirm.module.styl:
--------------------------------------------------------------------------------
1 | .confirm {
2 | width: 8rem //320px
3 | }
4 |
5 | .description {
6 | color: var(--primary-text-color)
7 | }
--------------------------------------------------------------------------------
/src/co/overlay/dialog/view/loading.js:
--------------------------------------------------------------------------------
1 | import s from './loading.module.styl'
2 | import React from 'react'
3 | import Modal, { Content } from '~co/overlay/modal'
4 | import Preloader from '~co/common/preloader'
5 |
6 | export default function DialogLoading() {
7 | return (
8 |
11 |
12 |
13 |
14 |
15 | )
16 | }
--------------------------------------------------------------------------------
/src/co/overlay/dialog/view/loading.module.styl:
--------------------------------------------------------------------------------
1 | .loading, .content {
2 | border-radius: 100%
3 | }
4 |
5 | .content {
6 | display: flex
7 | padding: var(--padding-medium)
8 | }
--------------------------------------------------------------------------------
/src/co/overlay/dialog/view/prompt.module.styl:
--------------------------------------------------------------------------------
1 | .prompt {
2 | width: 10rem //400px
3 | }
4 |
5 | .description {
6 | color: var(--primary-text-color)
7 | }
--------------------------------------------------------------------------------
/src/co/overlay/modal/content.js:
--------------------------------------------------------------------------------
1 | import s from './content.module.styl'
2 | import React from 'react'
3 |
4 | //data-stretch
5 | function ModalContent({ as='div', forwardedRef, children, className='', ...etc }) {
6 | const Component = as
7 |
8 | return (
9 |
10 | {children}
11 |
12 | )
13 | }
14 |
15 | export default React.forwardRef((props, ref) => {
16 | return
17 | })
--------------------------------------------------------------------------------
/src/co/overlay/modal/content.module.styl:
--------------------------------------------------------------------------------
1 | .content {
2 | flex: 1
3 | min-height: 0
4 | overflow-x: clip
5 | overflow-y: auto
6 | overscroll-behavior: contain
7 | position: relative
8 |
9 | &[data-indent] {
10 | padding: var(--padding-medium)
11 | padding-top: 0
12 | }
13 | }
--------------------------------------------------------------------------------
/src/co/overlay/modal/context.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | const Context = React.createContext({})
3 |
4 | export default Context
--------------------------------------------------------------------------------
/src/co/overlay/modal/header.module.styl:
--------------------------------------------------------------------------------
1 | @media screen and (max-width: 500px) {
2 | .close {
3 | display: none
4 | }
5 | }
6 |
7 | @media screen and (min-width: 500px) {
8 | .back {
9 | display: none
10 | }
11 | }
--------------------------------------------------------------------------------
/src/co/overlay/popover/context.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | const Context = React.createContext({})
3 |
4 | export default Context
--------------------------------------------------------------------------------
/src/co/overlay/popover/menu/index.js:
--------------------------------------------------------------------------------
1 | import s from './index.module.styl'
2 | import React from 'react'
3 |
4 | export function MenuInner({ children, forwardedRef, className='', ...etc }) {
5 | return (
6 |
7 | {children}
8 |
9 | )
10 | }
11 |
12 | export const Menu = React.forwardRef((props, ref) => {
13 | return
14 | })
15 |
16 | export * from './item'
17 | export * from './separator'
18 | export * from './section'
--------------------------------------------------------------------------------
/src/co/overlay/popover/menu/index.module.styl:
--------------------------------------------------------------------------------
1 | .menu {
2 | overflow: hidden
3 | overflow: clip
4 | padding: var(--padding-mini) 0px
5 | }
--------------------------------------------------------------------------------
/src/co/overlay/popover/menu/item.module.styl:
--------------------------------------------------------------------------------
1 | .item[data-menu-item] {
2 | cursor: pointer
3 | height: var(--button-height)
4 |
5 | @media (pointer: fine) {
6 | &:focus, &:hover {
7 | background: var(--accent-color)
8 | color: var(--background-color) !important
9 | --secondary-text-color: var(--background-color)
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/co/overlay/popover/menu/section.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Section } from '~co/common/list'
3 |
4 | export function MenuSection(props){
5 | return
6 | }
--------------------------------------------------------------------------------
/src/co/overlay/popover/menu/separator.js:
--------------------------------------------------------------------------------
1 | import s from './separator.module.styl'
2 | import React from 'react'
3 |
4 | export function MenuSeparator(){
5 | return
6 | }
--------------------------------------------------------------------------------
/src/co/overlay/popover/menu/separator.module.styl:
--------------------------------------------------------------------------------
1 | .separator {
2 | box-shadow: inset 0 var(--shadow-height) 0 var(--shadow-color)
3 | height: 1px
4 | display: block;
5 | margin: 5px 0px;
6 | }
--------------------------------------------------------------------------------
/src/co/picker/file/base/progress.module.styl:
--------------------------------------------------------------------------------
1 | .title {
2 | font-size: var(--title-font-size)
3 | font-weight: 600
4 |
5 | }
6 |
7 | .failed {
8 | margin-top: var(--padding-small)
9 | color: var(--danger-color)
10 | }
--------------------------------------------------------------------------------
/src/co/picker/file/drop/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import withBase from '../base'
3 | import Module from './module'
4 |
5 | class PickerSourceDrop extends React.Component {
6 | static defaultProps = {
7 | //..same as ../base
8 | }
9 |
10 | render() {
11 | return (
12 |
13 | )
14 | }
15 | }
16 |
17 | export default withBase(PickerSourceDrop)
--------------------------------------------------------------------------------
/src/co/picker/icon/add.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import t from '~t'
3 | import PickerFile from '~co/picker/file/element'
4 |
5 | import Button from '~co/common/button'
6 | import Icon from '~co/common/icon'
7 |
8 | export default function PickerIconAdd(props) {
9 | return (
10 |
18 | )
19 | }
--------------------------------------------------------------------------------
/src/co/picker/icon/reset.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import t from '~t'
3 |
4 | import Button from '~co/common/button'
5 |
6 | export default class PickerIconReset extends React.Component {
7 | onClick = (e)=>{
8 | e.preventDefault()
9 | this.props.onLink('')
10 | }
11 |
12 | render() {
13 | return (
14 |
20 | )
21 | }
22 | }
--------------------------------------------------------------------------------
/src/co/picker/link/index.module.styl:
--------------------------------------------------------------------------------
1 | .popover {
2 | width: 10rem //400px
3 | }
--------------------------------------------------------------------------------
/src/co/picker/tags/index.module.styl:
--------------------------------------------------------------------------------
1 | .field {
2 | width: 400px
3 | min-height: 200px
4 | }
--------------------------------------------------------------------------------
/src/co/screen/basic/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from '~co/common/header'
3 |
4 | export default (props)=>(
5 |
6 | )
--------------------------------------------------------------------------------
/src/co/screen/error/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import t from '~t'
3 | import Basic from '../basic'
4 |
5 | const refresh = (e)=>{
6 | e.preventDefault()
7 | window.location.reload()
8 | }
9 |
10 | export default ()=>(
11 |
12 |
13 |
{t.s('server')}
14 |
15 | {t.s('noInternetError')}
16 | {t.s('refresh')}
17 |
18 |
19 |
20 |
21 | )
--------------------------------------------------------------------------------
/src/co/screen/splitview/helpers/small.module.styl:
--------------------------------------------------------------------------------
1 | .small[data-small="false"] {
2 | :global(.show-on-small-body) {
3 | display: none
4 | }
5 | }
6 |
7 | .small[data-small="true"] {
8 | :global(.hide-on-small-body) {
9 | display: none
10 | }
11 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/main/content.js:
--------------------------------------------------------------------------------
1 | import s from './content.module.styl'
2 | import React from 'react'
3 |
4 | export default class SplitViewMainContent extends React.Component {
5 | render() {
6 | const { children, ...other } = this.props
7 |
8 | return (
9 |
10 | {children}
11 |
12 | )
13 | }
14 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/main/content.module.styl:
--------------------------------------------------------------------------------
1 | .content {
2 | flex: 1;
3 | min-height: 0
4 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/main/footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class SplitViewMainFooter extends React.Component {
4 | render() {
5 | const { children, ...other } = this.props
6 |
7 | return (
8 |
11 | )
12 | }
13 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/main/index.module.styl:
--------------------------------------------------------------------------------
1 | :global(.svMain) {
2 | contain: strict
3 |
4 | display: flex;
5 | flex-direction: column;
6 | z-index: 11;
7 | box-shadow: var(--shadow-height) 0 0 var(--shadow-color);
8 |
9 | background: var(--background-color);
10 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/reader/content.js:
--------------------------------------------------------------------------------
1 | import s from './content.module.styl'
2 | import React from 'react'
3 |
4 | export default class SplitViewReaderContent extends React.Component {
5 | render() {
6 | return (
7 |
8 | {this.props.children}
9 |
10 | )
11 | }
12 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/reader/content.module.styl:
--------------------------------------------------------------------------------
1 | .content {
2 | flex: 1
3 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/reader/footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Footer from '~co/common/footer'
3 |
4 | function SplitViewReaderFooter(props) {
5 | return
6 | }
7 |
8 | SplitViewReaderFooter.defaultProps = {
9 | 'data-fancy': true
10 | }
11 |
12 | export default SplitViewReaderFooter
--------------------------------------------------------------------------------
/src/co/screen/splitview/reader/index.module.styl:
--------------------------------------------------------------------------------
1 | :global(.svReader) {
2 | contain: strict
3 |
4 | background-color: var(--background-color)
5 | overflow: auto
6 | overflow: overlay
7 | position: relative
8 |
9 | display: flex
10 | flex-direction: column
11 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/sidebar/backdrop.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Context } from '../'
3 |
4 | export default class SplitViewSidebarBackdrop extends React.Component {
5 | static contextType = Context
6 |
7 | render() {
8 | return (
9 |
12 | )
13 | }
14 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/sidebar/content.js:
--------------------------------------------------------------------------------
1 | import s from './content.module.styl'
2 | import React from 'react'
3 |
4 | export default function SplitViewSidebarContent({ className='', children }) {
5 | return (
6 |
7 | {children}
8 |
9 | )
10 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/sidebar/content.module.styl:
--------------------------------------------------------------------------------
1 | .content {
2 | contain: strict
3 | flex: 1;
4 | min-height: 0;
5 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/sidebar/footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class SplitViewSidebarFooter extends React.Component {
4 | render() {
5 | return (
6 |
9 | )
10 | }
11 | }
--------------------------------------------------------------------------------
/src/co/screen/splitview/sidebar/index.module.styl:
--------------------------------------------------------------------------------
1 | :global(.svSidebar) {
2 | display: flex;
3 | flex-direction: column;
4 | --background-color: var(--sidebar-background-color);
5 | background-color: var(--background-color);
6 | position: relative;
7 | will-change: width;
8 |
9 | &:after {
10 | content: '';
11 | position: absolute;
12 | right: 0;
13 | top: 0;
14 | bottom: 0;
15 | z-index: 10;
16 | width: 1px;
17 | box-shadow: inset calc(var(--shadow-height) * -1) 0 0 var(--shadow-color);
18 | }
19 | }
20 |
21 | :global(.svSidebarBackdrop) {
22 | background: rgba(0,0,0,.25);
23 | }
--------------------------------------------------------------------------------
/src/co/search/field/index.module.styl:
--------------------------------------------------------------------------------
1 | .field {
2 | width: 100vw
3 | max-width: 100%
4 | }
--------------------------------------------------------------------------------
/src/co/search/form/index.module.styl:
--------------------------------------------------------------------------------
1 | .form {
2 | width: 100%
3 | min-width: 0
4 | max-width: unquote('max(10rem, calc(var(--value-ch) + 2rem))') //400
5 | }
--------------------------------------------------------------------------------
/src/co/search/menu/help/index.module.styl:
--------------------------------------------------------------------------------
1 | .help {
2 | height: auto
3 | margin-top: var(--padding-small)
4 | padding: var(--padding-small) var(--padding-medium)
5 | padding-right: var(--padding-small)
6 | }
7 |
8 | .tip {
9 | flex: initial
10 | max-width: 6rem
11 | white-space: initial
12 | }
13 |
14 | .actions {
15 | flex: 1
16 | display: block
17 | text-align: right
18 | }
--------------------------------------------------------------------------------
/src/co/search/menu/recent/item.module.styl:
--------------------------------------------------------------------------------
1 | .icon {
2 | display: flex
3 | align-items: center
4 | justify-content: center
5 | position: relative
6 | color: var(--tag-color)
7 | }
--------------------------------------------------------------------------------
/src/co/search/menu/recent/section.module.styl:
--------------------------------------------------------------------------------
1 | .section:not(:first-child) {
2 | margin-top: var(--padding-small)
3 | }
--------------------------------------------------------------------------------
/src/co/search/menu/suggestions/tip.js:
--------------------------------------------------------------------------------
1 | import s from './tip.module.styl'
2 | import React from 'react'
3 | import t from '~t'
4 | import { Section } from '~co/common/list'
5 |
6 | export default function SearchMenuSuggestionsTip({ suggestions, value }) {
7 | if (!suggestions.length)
8 | return null
9 |
10 | if (suggestions[0].query?.startsWith('created'))
11 | return ()
12 |
13 | if (suggestions[0]._id != 'current')
14 | return ({value ? t.s('narrowSearch') : t.s('suggested')})
15 |
16 | return null
17 | }
--------------------------------------------------------------------------------
/src/co/search/menu/suggestions/tip.module.styl:
--------------------------------------------------------------------------------
1 | .tip {
2 | height: auto
3 | padding: var(--padding-small) var(--padding-medium)
4 | max-width: 8.5rem
5 | }
--------------------------------------------------------------------------------
/src/co/search/useSpaceId.js:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 | import { useSelector } from 'react-redux'
3 |
4 | export default function useSpaceId({ originalSpaceId, originalValue }) {
5 | const incollection = useSelector(state=>state.config.raindrops_search_incollection)
6 |
7 | return useMemo(()=>{
8 | if(originalValue || incollection || !parseInt(originalSpaceId))
9 | return originalSpaceId
10 |
11 | return 0
12 | }, [incollection, originalSpaceId, originalValue])
13 | }
--------------------------------------------------------------------------------
/src/co/tags/autocomplete/index.module.styl:
--------------------------------------------------------------------------------
1 | .tags {
2 | --lazy-item-height: var(--list-item-height)
3 | }
--------------------------------------------------------------------------------
/src/co/tags/item/icon.js:
--------------------------------------------------------------------------------
1 | import s from './icon.module.styl'
2 | import React from 'react'
3 |
4 | import { ItemIcon } from '~co/common/list'
5 | import Icon from '~co/common/icon'
6 |
7 | export default function TagsItemIcon() {
8 | return (
9 |
10 |
13 |
14 | )
15 | }
--------------------------------------------------------------------------------
/src/co/tags/item/icon.module.styl:
--------------------------------------------------------------------------------
1 | .icon {
2 | display: flex
3 | align-items: center
4 | justify-content: center
5 | position: relative
6 | color: var(--tag-color)
7 |
8 | // &:before {
9 | // content: ''
10 | // position: absolute
11 | // left: 0
12 | // right: 0
13 | // top: 0
14 | // bottom: 0
15 | // background: currentColor
16 | // opacity: .12
17 | // border-radius: var(--border-radius)
18 | // }
19 | }
--------------------------------------------------------------------------------
/src/co/tags/item/view.module.styl:
--------------------------------------------------------------------------------
1 | .item {
2 | cursor: pointer
3 | }
--------------------------------------------------------------------------------
/src/co/tags/section/view.module.styl:
--------------------------------------------------------------------------------
1 | .section {
2 | cursor: pointer
3 | }
--------------------------------------------------------------------------------
/src/co/user/about/index.js:
--------------------------------------------------------------------------------
1 | import s from './index.module.styl'
2 | import React from 'react'
3 | import { useSelector } from 'react-redux'
4 | import { user as getUser } from '~data/selectors/user'
5 | import { Avatar } from '~co/common/icon'
6 |
7 | export default function UserAbout({ className='' }) {
8 | const user = useSelector(state=>getUser(state))
9 |
10 | return (
11 |
12 |
16 |
17 |
{user.name}
18 |
{user.email}
19 |
20 | )
21 | }
--------------------------------------------------------------------------------
/src/co/user/about/index.module.styl:
--------------------------------------------------------------------------------
1 | .about {
2 | display: grid
3 | grid-template-columns: auto 1fr
4 | grid-column-gap: var(--padding-small)
5 | grid-template-areas: 'avatar name'\
6 | 'avatar email'
7 | }
8 |
9 | .avatar {
10 | grid-area: avatar
11 | }
12 |
13 | .name {
14 | grid-area: name
15 | font-size: var(--title-font-size)
16 | font-weight: 600
17 | }
18 |
19 | .email {
20 | grid-area: email
21 | font-size: var(--secondary-font-size)
22 | color: var(--secondary-text-color)
23 | }
--------------------------------------------------------------------------------
/src/co/user/profile/index.js:
--------------------------------------------------------------------------------
1 | import Button from './button'
2 | import Menu from './menu'
3 |
4 | export {
5 | Button,
6 | Menu
7 | }
--------------------------------------------------------------------------------
/src/co/virtual/lazy/item.module.css:
--------------------------------------------------------------------------------
1 | .item {}
2 |
3 | /* height mode */
4 | .height {
5 | /* content-visibility: auto is buggy when height is very different between items!!! */
6 | /* content-visibility: auto;
7 | contain-intrinsic-size: 0 var(--lazy-item-height, 0); */
8 | }
9 |
10 | .height:empty {
11 | height: var(--lazy-item-height, 0px);
12 | }
13 |
14 | /* grid mode */
15 | .row-end-span {
16 | grid-row-end: span 1
17 | }
--------------------------------------------------------------------------------
/src/co/virtual/tree/index.module.styl:
--------------------------------------------------------------------------------
1 | [data-rbd-draggable-id] {
2 | will-change: transform, opacity
3 | }
4 |
5 | .tree {
6 | outline: none
7 | }
--------------------------------------------------------------------------------
/src/config/csp.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | hosts: 'https://app.raindrop.io https://raindrop.onfastspring.com '+(process.env.SENTRY_RELEASE ? 'https://*.sentry.io https://sentry.io' : '')
3 | }
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | import csp from './csp'
2 | import links from './links'
3 | import vendors from './vendors'
4 |
5 | export default {
6 | csp,
7 | links,
8 | vendors
9 | }
--------------------------------------------------------------------------------
/src/config/vendors.js:
--------------------------------------------------------------------------------
1 | const vendors = {}
2 |
3 | if (process.env.SENTRY_RELEASE)
4 | vendors.sentry = {
5 | dsn: 'https://c647a147102b4de68dd9dd8690e06840@o199199.ingest.sentry.io/5264532'
6 | }
7 |
8 | export default vendors
--------------------------------------------------------------------------------
/src/data/README.md:
--------------------------------------------------------------------------------
1 | # Actions
2 | ## Collections
3 | - Reorder collection
4 | `oneReorder(_id, {after: someCollectionId})` or `oneReorder(_id, {before: someCollectionId})`
5 |
6 | - Move collection to group or as nested child
7 | `oneReorder(_id, {to: groupId | collectionId})`
--------------------------------------------------------------------------------
/src/data/TODO.md:
--------------------------------------------------------------------------------
1 | - draftLoad can override unsaved changes
--------------------------------------------------------------------------------
/src/data/actions/backups.js:
--------------------------------------------------------------------------------
1 | import * as c from '../constants/backups'
2 |
3 | export const load = ()=>({
4 | type: c.BACKUPS_LOAD_REQ
5 | })
--------------------------------------------------------------------------------
/src/data/actions/bookmarks/highlights.js:
--------------------------------------------------------------------------------
1 | import { BOOKMARK_HIGHLIGH_ADD, BOOKMARK_HIGHLIGH_UPDATE, BOOKMARK_HIGHLIGH_REMOVE } from '../../constants/bookmarks'
2 |
3 | //Drafts
4 | export const highlightAdd = (bookmarkId, newOne={})=>({
5 | type: BOOKMARK_HIGHLIGH_ADD,
6 | bookmarkId,
7 | newOne //{ text, note, ... }
8 | })
9 |
10 | export const highlightUpdate = (bookmarkId, _id, changed)=>({
11 | type: BOOKMARK_HIGHLIGH_UPDATE,
12 | bookmarkId,
13 | _id,
14 | changed //{text, ...}
15 | })
16 |
17 | export const highlightRemove = (bookmarkId, _id)=>({
18 | type: BOOKMARK_HIGHLIGH_REMOVE,
19 | bookmarkId,
20 | _id
21 | })
--------------------------------------------------------------------------------
/src/data/actions/bookmarks/html.js:
--------------------------------------------------------------------------------
1 | import { BOOKMARK_HTML_LOAD_REQ } from '../../constants/bookmarks'
2 |
3 | export const htmlLoad = _id=>({
4 | type: BOOKMARK_HTML_LOAD_REQ,
5 | _id: parseInt(_id)
6 | })
--------------------------------------------------------------------------------
/src/data/actions/bookmarks/index.js:
--------------------------------------------------------------------------------
1 | export * from './space'
2 | export * from './single'
3 | export * from './draft'
4 | export * from './selectMode'
5 | export * from './html'
6 | export * from './recent'
7 | export * from './highlights'
--------------------------------------------------------------------------------
/src/data/actions/bookmarks/recent.js:
--------------------------------------------------------------------------------
1 | import { RECENT_SEARCH_CLEAR_REQ } from '../../constants/bookmarks'
2 |
3 | export const recentSearchClear = ()=>({
4 | type: RECENT_SEARCH_CLEAR_REQ,
5 | })
--------------------------------------------------------------------------------
/src/data/actions/config.js:
--------------------------------------------------------------------------------
1 | import { CONFIG_ACKNOWLEDGE } from '../constants/config'
2 | import { USER_UPDATE_REQ } from '../constants/user'
3 |
4 | //set('key', val) or set({ obj... })
5 | export const set = (key, val)=>({
6 | type: USER_UPDATE_REQ,
7 | user: {
8 | config: typeof key == 'object' ?
9 | key :
10 | { [key]: val }
11 | }
12 | })
13 |
14 | export const hideSection = (section, value)=>({
15 | type: USER_UPDATE_REQ,
16 | user: {
17 | config: {
18 | [`${section}_hide`]: value
19 | }
20 | }
21 | })
22 |
23 | export const acknowledge = (key)=>({
24 | type: CONFIG_ACKNOWLEDGE,
25 | key
26 | })
--------------------------------------------------------------------------------
/src/data/actions/covers.js:
--------------------------------------------------------------------------------
1 | import { COVERS_LOAD_REQ } from '../constants/covers'
2 |
3 | export const load = (query='')=>({
4 | type: COVERS_LOAD_REQ,
5 | query
6 | })
--------------------------------------------------------------------------------
/src/data/actions/filters.js:
--------------------------------------------------------------------------------
1 | import {
2 | FILTERS_LOAD_PRE,
3 | FILTERS_AUTOLOAD,
4 | } from '../constants/filters'
5 |
6 | export const autoLoad = (spaceId, enabled=false)=>({
7 | type: FILTERS_AUTOLOAD,
8 | spaceId: String(spaceId),
9 | enabled
10 | })
11 |
12 | export const load = (spaceId, query)=>({
13 | type: FILTERS_LOAD_PRE,
14 | spaceId: String(spaceId),
15 | query: query || {}
16 | })
--------------------------------------------------------------------------------
/src/data/actions/import.js:
--------------------------------------------------------------------------------
1 | import wrapFunc from '../utils/wrapFunc'
2 | import * as c from '../constants/import'
3 |
4 | export const upload = (file, onSuccess, onFail)=>({
5 | type: c.IMPORT_FILE_UPLOAD_REQ,
6 | file,
7 | onSuccess: wrapFunc(onSuccess),
8 | onFail: wrapFunc(onFail)
9 | })
10 |
11 | export const setMode = (mode)=>({
12 | type: c.IMPORT_SET_MODE,
13 | mode
14 | })
15 |
16 | export const parcelSave = (onSuccess, onFail)=>({
17 | type: c.IMPORT_PARCEL_SAVE_REQ,
18 | onSuccess: wrapFunc(onSuccess),
19 | onFail: wrapFunc(onFail)
20 | })
21 |
22 | export const cancel = ()=>({
23 | type: c.IMPORT_CANCEL
24 | })
--------------------------------------------------------------------------------
/src/data/actions/predictions.js:
--------------------------------------------------------------------------------
1 | import * as c from '../constants/predictions'
2 | import wrapFunc from '../utils/wrapFunc'
3 |
4 | export const load = ()=>({
5 | type: c.PREDICTIONS_LOAD_REQ
6 | })
7 |
8 | export const patch = (details)=>({
9 | type: c.PREDICTION_PATCH,
10 | ...details
11 | })
12 |
13 | export const apply = (_id, onSuccess, onFail)=>({
14 | type: c.PREDICTION_APPLY_REQ,
15 | _id,
16 | onSuccess: wrapFunc(onSuccess),
17 | onFail: wrapFunc(onFail)
18 | })
--------------------------------------------------------------------------------
/src/data/actions/rate.js:
--------------------------------------------------------------------------------
1 | import { RATE_LOAD } from '../constants/rate'
2 | import { CONFIG_ACKNOWLEDGE } from '../constants/config'
3 |
4 | //platform: extension_(chrome|opera|edge|safari|firefox), mobile_(ios|android)
5 | export const load = platform=>({
6 | type: RATE_LOAD,
7 | platform
8 | })
9 |
10 | export const acknowledge = platform=>({
11 | type: CONFIG_ACKNOWLEDGE,
12 | key: `rate_${platform}`
13 | })
--------------------------------------------------------------------------------
/src/data/actions/tags.js:
--------------------------------------------------------------------------------
1 | import wrapFunc from '../utils/wrapFunc'
2 | import {
3 | TAG_RENAME_REQ,
4 | TAG_REMOVE_REQ,
5 | TAGS_REORDER
6 | } from '../constants/tags'
7 |
8 | export const oneRemove = (tagName, onSuccess, onFail)=>({
9 | type: TAG_REMOVE_REQ,
10 | tagName,
11 | onSuccess: wrapFunc(onSuccess),
12 | onFail: wrapFunc(onFail)
13 | })
14 |
15 | export const oneRename = (tagName, newName, onSuccess, onFail)=>({
16 | type: TAG_RENAME_REQ,
17 | tagName,
18 | newName,
19 | onSuccess: wrapFunc(onSuccess),
20 | onFail: wrapFunc(onFail)
21 | })
22 |
23 | export const reorder = (method)=>({
24 | type: TAGS_REORDER,
25 | method
26 | })
--------------------------------------------------------------------------------
/src/data/constants/app.js:
--------------------------------------------------------------------------------
1 | export const
2 | APP_DOMAIN = 'raindrop.io'
3 |
4 | export const
5 | APP_BASE_URL = `https://${APP_DOMAIN}`,
6 | WORKERS_BASE_URL = 'https://rdl.ink',
7 | LEGACY_WORKERS_BASE_URL=`https://stella.${APP_DOMAIN}`
8 |
9 | export const
10 | API_ENDPOINT_URL = `${process.env.NODE_ENV == 'production' || RAINDROP_ENVIRONMENT == 'react-native' ? 'https://api.raindrop.io' : 'http://localhost:3000' }/v1/`,
11 | API_RETRIES = 3,
12 | API_TIMEOUT = 30000,
13 | FAVICON_URL = `${WORKERS_BASE_URL}/favicon`,
14 | RENDER_URL = `${WORKERS_BASE_URL}/render`,
15 | PREVIEW_URL = 'https://preview.systems'
--------------------------------------------------------------------------------
/src/data/constants/backups.js:
--------------------------------------------------------------------------------
1 | export const
2 | BACKUPS_LOAD_REQ = 'BACKUPS_LOAD_REQ',
3 | BACKUPS_LOAD_SUCCESS = 'BACKUPS_LOAD_SUCCESS',
4 | BACKUPS_LOAD_ERROR = 'BACKUPS_LOAD_ERROR'
--------------------------------------------------------------------------------
/src/data/constants/config.js:
--------------------------------------------------------------------------------
1 | export const
2 | CONFIG_ACKNOWLEDGE = 'CONFIG_ACKNOWLEDGE'
--------------------------------------------------------------------------------
/src/data/constants/covers.js:
--------------------------------------------------------------------------------
1 | export const
2 | COVERS_LOAD_REQ = 'COVERS_LOAD_REQ',
3 | COVERS_LOAD_SUCCESS = 'COVERS_LOAD_SUCCESS',
4 | COVERS_LOAD_ERROR = 'COVERS_LOAD_ERROR'
--------------------------------------------------------------------------------
/src/data/constants/filters.js:
--------------------------------------------------------------------------------
1 | export const
2 | FILTERS_AUTOLOAD = 'FILTERS_AUTOLOAD',
3 | FILTERS_LOAD_PRE = 'FILTERS_LOAD_PRE',
4 | FILTERS_LOAD_REQ = 'FILTERS_LOAD_REQ',
5 | FILTERS_LOAD_SUCCESS = 'FILTERS_LOAD_SUCCESS',
6 | FILTERS_LOAD_ERROR = 'FILTERS_LOAD_ERROR',
7 | FILTERS_RESET = 'FILTERS_RESET'
--------------------------------------------------------------------------------
/src/data/constants/import.js:
--------------------------------------------------------------------------------
1 | export const
2 | IMPORT_CANCEL = 'IMPORT_CANCEL',
3 | IMPORT_FILE_UPLOAD_REQ = 'IMPORT_FILE_UPLOAD_REQ',
4 | IMPORT_FILE_UPLOAD_SUCCESS = 'IMPORT_FILE_UPLOAD_SUCCESS',
5 | IMPORT_FILE_UPLOAD_ERROR = 'IMPORT_FILE_UPLOAD_ERROR',
6 | IMPORT_SET_MODE = 'IMPORT_SET_MODE',
7 | IMPORT_PARCEL_SAVE_REQ = 'IMPORT_PARCEL_SAVE_REQ',
8 | IMPORT_PARCEL_SAVE_PROGRESS = 'IMPORT_PARCEL_SAVE_PROGRESS',
9 | IMPORT_PARCEL_SAVE_SUCCESS = 'IMPORT_PARCEL_SAVE_SUCCESS',
10 | IMPORT_PARCEL_SAVE_ERROR = 'IMPORT_PARCEL_SAVE_ERROR'
--------------------------------------------------------------------------------
/src/data/constants/predictions.js:
--------------------------------------------------------------------------------
1 | export const
2 | PREDICTIONS_LOAD_REQ = 'PREDICTIONS_LOAD_REQ',
3 | PREDICTIONS_LOAD_SUCCESS = 'PREDICTIONS_LOAD_SUCCESS',
4 | PREDICTIONS_LOAD_ERROR = 'PREDICTIONS_LOAD_ERROR',
5 |
6 | PREDICTION_PATCH = 'PREDICTION_PATCH',
7 |
8 | PREDICTION_APPLY_REQ = 'PREDICTION_APPLY_REQ',
9 | PREDICTION_APPLY_SUCCESS = 'PREDICTION_APPLY_SUCCESS',
10 | PREDICTION_APPLY_ERROR = 'PREDICTION_APPLY_ERROR'
--------------------------------------------------------------------------------
/src/data/constants/rate.js:
--------------------------------------------------------------------------------
1 | export const
2 | RATE_LOAD = 'RATE_LOAD'
--------------------------------------------------------------------------------
/src/data/constants/tags.js:
--------------------------------------------------------------------------------
1 | export const
2 | TAGS_LOAD_SUCCESS = 'TAGS_LOAD_SUCCESS',
3 | TAGS_LOAD_ERROR = 'TAGS_LOAD_ERROR',
4 |
5 | TAGS_REORDER = 'TAGS_REORDER',
6 |
7 | TAGS_RECENT_LOAD_REQ = 'TAGS_RECENT_LOAD_REQ',
8 | TAGS_RECENT_LOAD_SUCCESS = 'TAGS_RECENT_LOAD_SUCCESS',
9 | TAGS_RECENT_LOAD_ERROR = 'TAGS_RECENT_LOAD_ERROR',
10 |
11 | TAG_RENAME_REQ = 'TAG_RENAME_REQ',
12 | TAG_RENAME_SUCCESS = 'TAG_RENAME_SUCCESS',
13 | TAG_RENAME_ERROR = 'TAG_RENAME_ERROR',
14 |
15 | TAG_REMOVE_REQ = 'TAG_REMOVE_REQ',
16 | TAG_REMOVE_SUCCESS = 'TAG_REMOVE_SUCCESS',
17 | TAG_REMOVE_ERROR = 'TAG_REMOVE_ERROR'
--------------------------------------------------------------------------------
/src/data/helpers/bookmarks/getUrl.js:
--------------------------------------------------------------------------------
1 | import { blankSpace } from './blankSpace'
2 | import {SPACE_PER_PAGE} from '../../constants/bookmarks'
3 | import _ from 'lodash-es'
4 |
5 | export const getUrlQuery = (query)=>{
6 | const entities = _.compact(_.map(query||blankSpace.query, (val,key)=>{
7 | if (val)
8 | switch(key){
9 | case 'page':
10 | case 'sort':
11 | return key+'='+encodeURIComponent(val);
12 |
13 | case 'search':
14 | return key+'='+encodeURIComponent(val);
15 | }
16 | }))
17 | entities.push('perpage='+SPACE_PER_PAGE)
18 |
19 | if (!entities.length)
20 | return ''
21 |
22 | return '?'+entities.join('&')
23 | }
24 |
25 | export const getUrl = (__id, query)=>
26 | `${parseInt(__id)||0}${getUrlQuery(query)}`
--------------------------------------------------------------------------------
/src/data/helpers/bookmarks/normalizeRecentSearch.js:
--------------------------------------------------------------------------------
1 | export const normalizeRecentSearch = (search={})=>{
2 | const _id = String(search._id)
3 |
4 | return ({
5 | _id,
6 | collectionRef: parseInt(search.collectionRef),
7 | query: _id+' ',
8 | date: search.date
9 | })
10 | }
--------------------------------------------------------------------------------
/src/data/helpers/bookmarks/queryIsEqual.js:
--------------------------------------------------------------------------------
1 | export const queryIsEqual = function(original={}, newone={}) {
2 | const entries = Object.entries(newone)
3 |
4 | for(const [key, val] of entries)
5 | if (original[key] != val)
6 | return false
7 |
8 | return true
9 | }
--------------------------------------------------------------------------------
/src/data/helpers/defaults.js:
--------------------------------------------------------------------------------
1 | export const appendAfterArray = (a, val, index)=>{
2 | return [
3 | ...a.slice(0, index),
4 | val,
5 | ...a.slice(index)
6 | ]
7 | }
--------------------------------------------------------------------------------
/src/data/helpers/filters/blankSpace.js:
--------------------------------------------------------------------------------
1 | import Immutable from 'seamless-immutable'
2 |
3 | export const blankSpace = Immutable({
4 | lastAction: null,
5 | version: '',
6 | status: 'idle', //idle, loading, loaded, error
7 | items: [], //{_id, count, query}
8 | query: { search: '' } //used only to prevent overrides
9 | })
--------------------------------------------------------------------------------
/src/data/helpers/filters/index.js:
--------------------------------------------------------------------------------
1 | export * from './blankSpace'
2 | export * from './normalizeItems'
--------------------------------------------------------------------------------
/src/data/helpers/oauth/blankClient.js:
--------------------------------------------------------------------------------
1 | import { normalizeClient } from './normalizeClient'
2 |
3 | export const blankClient = normalizeClient()
--------------------------------------------------------------------------------
/src/data/helpers/oauth/index.js:
--------------------------------------------------------------------------------
1 | export * from './blankClient'
2 | export * from './normalizeClient'
--------------------------------------------------------------------------------
/src/data/helpers/oauth/normalizeClient.js:
--------------------------------------------------------------------------------
1 | export const normalizeClient = function(client={}) {
2 | return {
3 | _id: String(client._id),
4 | name: client.name || '',
5 | icon: client.icon || '',
6 | site: client.site || '',
7 | description: client.description || '',
8 |
9 | redirects: client.redirects || [],
10 | secret: client.secret || ''
11 | }
12 | }
--------------------------------------------------------------------------------
/src/data/helpers/tags/blankSpace.js:
--------------------------------------------------------------------------------
1 | import Immutable from 'seamless-immutable'
2 |
3 | export const blankSpace = Immutable({
4 | lastAction: null,
5 | version: '',
6 | status: 'idle', //idle, loading, loaded, error
7 | tags: [], //{_id, count, query}
8 | query: { search: '' }, //used only to prevent overrides
9 | })
--------------------------------------------------------------------------------
/src/data/helpers/tags/index.js:
--------------------------------------------------------------------------------
1 | export * from './blankSpace'
2 | export * from './normalizeTag'
3 | export * from './normalizeTags'
--------------------------------------------------------------------------------
/src/data/helpers/tags/normalizeTag.js:
--------------------------------------------------------------------------------
1 | export const normalizeTag = (tag={})=>{
2 | const _id = String(tag._id || tag.name)
3 |
4 | return ({
5 | _id,
6 | count: tag.count||0,
7 | query: _id.includes(' ') ? `"#${_id}" ` : `#${_id} `
8 | })
9 | }
--------------------------------------------------------------------------------
/src/data/helpers/tags/normalizeTags.js:
--------------------------------------------------------------------------------
1 | import { normalizeTag } from './normalizeTag'
2 |
3 | export const normalizeTags = tags=>
4 | (tags||[])
5 | .filter(obj=>obj && (obj._id || obj.name))
6 | .map(normalizeTag)
--------------------------------------------------------------------------------
/src/data/modules/error.js:
--------------------------------------------------------------------------------
1 | export default class ApiError extends Error {
2 | constructor({ status, error, errorMessage }) {
3 | if (!errorMessage)
4 | switch(status){
5 | case 401: errorMessage = 'Login is required'; break
6 | case 408: errorMessage = 'timeout'; break
7 | case 400: errorMessage = 'validation failed'; break
8 | case 404: errorMessage = 'not found'; break
9 | }
10 |
11 | super(errorMessage)
12 | this.code = error
13 | this.error = error
14 | this.status = status
15 | this.name = this.constructor.name
16 | }
17 | }
--------------------------------------------------------------------------------
/src/data/modules/format/cache_url.js:
--------------------------------------------------------------------------------
1 | import Api from '../api'
2 |
3 | export default async function(bookmarkID){
4 | const { link='' } = await Api._get(`raindrop/${bookmarkID}/cache?json=1`)
5 | return link
6 | }
--------------------------------------------------------------------------------
/src/data/modules/format/domain.js:
--------------------------------------------------------------------------------
1 | export default function(s='') {
2 | try{s = s.trim();} catch(e) {if(e)s='';}
3 | return s.replace(/^www\./, '')
4 | }
--------------------------------------------------------------------------------
/src/data/modules/format/favicon.js:
--------------------------------------------------------------------------------
1 | import { FAVICON_URL } from '../../constants/app'
2 |
3 | export default function(domain=''){
4 | if (!domain)
5 | return ''
6 |
7 | return `${FAVICON_URL}/${domain}`
8 | }
--------------------------------------------------------------------------------
/src/data/modules/format/iframeable_url.js:
--------------------------------------------------------------------------------
1 | import Api from '../api'
2 |
3 | export default async function(url){
4 | const { result=false } = await Api._get(`import/url/iframeable?url=${encodeURIComponent(url)}`)
5 | return result
6 | }
--------------------------------------------------------------------------------
/src/data/modules/format/screenshot.js:
--------------------------------------------------------------------------------
1 | import { WORKERS_BASE_URL, LEGACY_WORKERS_BASE_URL, RENDER_URL } from '../../constants/app'
2 |
3 | export default function(url='') {
4 | if (url.includes(WORKERS_BASE_URL) ||
5 | url.includes(LEGACY_WORKERS_BASE_URL))
6 | return url
7 |
8 | return RENDER_URL+'/'+encodeURIComponent(url)
9 | }
--------------------------------------------------------------------------------
/src/data/modules/format/thumb.js:
--------------------------------------------------------------------------------
1 | import normalizeURL from './url'
2 | import { RENDER_URL, WORKERS_BASE_URL, LEGACY_WORKERS_BASE_URL } from '../../constants/app'
3 |
4 | export default function(url='') {
5 | let finalURL = normalizeURL(url)
6 | if (!finalURL)
7 | return ''
8 |
9 | if (finalURL.includes(WORKERS_BASE_URL) ||
10 | finalURL.includes(LEGACY_WORKERS_BASE_URL))
11 | return finalURL.replace(/width=\d+/, 'a')
12 |
13 | return RENDER_URL+'/'+encodeURIComponent(finalURL)
14 | }
--------------------------------------------------------------------------------
/src/data/modules/format/url.js:
--------------------------------------------------------------------------------
1 | import { APP_BASE_URL } from '../../constants/app'
2 |
3 | export default function(s=''){
4 | try{s = s.trim();} catch(e) {if(e)s='';}
5 |
6 | if (s.indexOf('data:')==0)
7 | return ''
8 |
9 | if (s.indexOf('//')==0)
10 | return 'http:'+s;
11 | else if (s.indexOf('/')==0)
12 | return APP_BASE_URL+s;
13 | else
14 | return s;
15 | }
--------------------------------------------------------------------------------
/src/data/modules/user/isPro.js:
--------------------------------------------------------------------------------
1 | import { store } from '../../'
2 |
3 | export default ()=>{
4 | const state = store.getState()
5 | return state.user.current && state.user.current.pro
6 | }
--------------------------------------------------------------------------------
/src/data/reducers/index.js:
--------------------------------------------------------------------------------
1 | import user from './user'
2 | import backups from './backups'
3 | import collections from './collections'
4 | import bookmarks from './bookmarks'
5 | import filters from './filters'
6 | import tags from './tags'
7 | import covers from './covers'
8 | import config from './config'
9 | import oauth from './oauth'
10 | import predictions from './predictions'
11 | //import rate from './rate'
12 | import _import from './import'
13 |
14 | export default {
15 | user,
16 | backups,
17 | collections, //before bookmarks!
18 | bookmarks,
19 | filters,
20 | tags,
21 | covers,
22 | config,
23 | oauth,
24 | predictions,
25 | //rate,
26 | import: _import
27 | }
--------------------------------------------------------------------------------
/src/data/reducers/notes.md:
--------------------------------------------------------------------------------
1 | - Every reducer should have 'RESET' action
--------------------------------------------------------------------------------
/src/data/reducers/tags/recent.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash-es'
2 | import { normalizeTag } from '../../helpers/tags'
3 | import { REHYDRATE } from 'redux-persist/src/constants'
4 | import { TAGS_RECENT_LOAD_SUCCESS } from '../../constants/tags'
5 |
6 | export default function(state, action={}){switch (action.type) {
7 | case REHYDRATE:{
8 | const { recent=[] } = action.payload && action.payload.tags||{}
9 |
10 | return state.set('recent', recent)
11 | }
12 |
13 | case TAGS_RECENT_LOAD_SUCCESS:{
14 | const recent = action.tags.map(normalizeTag)
15 |
16 | if (_.isEqual(state.recent, recent))
17 | return state
18 |
19 | return state.set('recent', recent)
20 | }
21 | }}
--------------------------------------------------------------------------------
/src/data/sagas/backups.js:
--------------------------------------------------------------------------------
1 | import { put, takeLatest, call } from 'redux-saga/effects'
2 | import * as c from '../constants/backups'
3 | import Api from '../modules/api'
4 |
5 | export default function* () {
6 | yield takeLatest(c.BACKUPS_LOAD_REQ, load)
7 | }
8 |
9 | function* load() {
10 | try {
11 | const { items } = yield call(Api.get, 'backups')
12 |
13 | yield put({
14 | type: c.BACKUPS_LOAD_SUCCESS,
15 | items
16 | })
17 | } catch (error) {
18 | yield put({
19 | type: c.BACKUPS_LOAD_ERROR,
20 | error
21 | })
22 | }
23 | }
--------------------------------------------------------------------------------
/src/data/sagas/bookmarks/index.js:
--------------------------------------------------------------------------------
1 | import { all } from 'redux-saga/effects'
2 | import draft from './draft'
3 | import single from './single'
4 | import space from './space'
5 | import selectMode from './selectMode'
6 | import html from './html'
7 | import recent from './recent'
8 | import highlights from './highlights'
9 |
10 | export default function* () {
11 | yield all([
12 | space(),
13 | single(),
14 | draft(),
15 | selectMode(),
16 | html(),
17 | recent(),
18 | highlights()
19 | ])
20 | }
--------------------------------------------------------------------------------
/src/data/sagas/collections/index.js:
--------------------------------------------------------------------------------
1 | import { all } from 'redux-saga/effects'
2 | import items from './items'
3 | import groups from './groups'
4 | import single from './single'
5 | import drafts from './drafts'
6 | import sharing from './sharing'
7 | import selectMode from './selectMode'
8 |
9 | export default function* () {
10 | yield all([
11 | items(),
12 | groups(),
13 | single(),
14 | drafts(),
15 | sharing(),
16 | selectMode()
17 | ])
18 | }
--------------------------------------------------------------------------------
/src/data/sagas/covers.js:
--------------------------------------------------------------------------------
1 | import { call, put, takeLatest } from 'redux-saga/effects'
2 | import Api from '../modules/api'
3 | import {
4 | COVERS_LOAD_REQ, COVERS_LOAD_SUCCESS, COVERS_LOAD_ERROR
5 | } from '../constants/covers'
6 |
7 | //Requests
8 | export default function* () {
9 | yield takeLatest([
10 | COVERS_LOAD_REQ
11 | ], load)
12 | }
13 |
14 | function* load({ ignore=false, query='' }) {
15 | if (ignore)
16 | return;
17 |
18 | try {
19 | const { items } = yield call(Api.get, `collections/covers/${encodeURIComponent(query.trim())}`)
20 |
21 | yield put({
22 | type: COVERS_LOAD_SUCCESS,
23 | items
24 | });
25 | } catch (error) {
26 | yield put({
27 | type: COVERS_LOAD_ERROR,
28 | error
29 | });
30 | }
31 | }
--------------------------------------------------------------------------------
/src/data/sagas/tags/index.js:
--------------------------------------------------------------------------------
1 | import { all } from 'redux-saga/effects'
2 | import items from './items'
3 | import single from './single'
4 | import recent from './recent'
5 |
6 | export default function* () {
7 | yield all([
8 | items(),
9 | single(),
10 | recent()
11 | ])
12 | }
--------------------------------------------------------------------------------
/src/data/sagas/tags/items.js:
--------------------------------------------------------------------------------
1 | import { put, takeLatest } from 'redux-saga/effects'
2 |
3 | import {
4 | TAGS_REORDER,
5 | } from '../../constants/tags'
6 |
7 | import { USER_UPDATE_REQ } from '../../constants/user'
8 |
9 | //Requests
10 | export default function* () {
11 | //Reorder persist
12 | yield takeLatest([TAGS_REORDER], reorder)
13 | }
14 |
15 | function* reorder({ method }) {
16 | yield put({
17 | type: USER_UPDATE_REQ,
18 | user: {
19 | config: {
20 | tags_sort: method
21 | }
22 | }
23 | })
24 | }
--------------------------------------------------------------------------------
/src/data/selectors/bookmarks/html.js:
--------------------------------------------------------------------------------
1 | import { blankHtml } from '../../helpers/bookmarks'
2 |
3 | export const getHtml = ({bookmarks}, _id)=>{
4 | return bookmarks.html[_id] || blankHtml
5 | }
--------------------------------------------------------------------------------
/src/data/selectors/bookmarks/index.js:
--------------------------------------------------------------------------------
1 | export * from './space'
2 | export * from './single'
3 | export * from './draft'
4 | export * from './selectMode'
5 | export * from './html'
--------------------------------------------------------------------------------
/src/data/selectors/collections/drafts.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { normalizeCollection, blankDraft } from '../../helpers/collections'
3 |
4 | //Draft
5 | export const makeDraftItem = ()=>createSelector(
6 | [({collections={}}, _id)=>{
7 | if (!collections.getIn(['drafts', _id, 'item']))
8 | return normalizeCollection({_id: _id})
9 |
10 | return collections.drafts[_id].item
11 | }],
12 | (item)=>item
13 | )
14 |
15 | //Draft Status
16 | export const makeDraftStatus = ()=>createSelector(
17 | [({collections={}}, _id)=>{
18 | if (!collections.getIn(['drafts', _id, 'status']))
19 | return blankDraft.status
20 |
21 | return collections.drafts[_id].status
22 | }],
23 | (status)=>status
24 | )
--------------------------------------------------------------------------------
/src/data/selectors/collections/groups.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { getGroup } from '../../helpers/collections'
3 |
4 | export const group = createSelector(
5 | [({collections={}})=>collections.groups, (state,_id)=>_id],
6 | getGroup
7 | )
--------------------------------------------------------------------------------
/src/data/selectors/collections/index.js:
--------------------------------------------------------------------------------
1 | export * from './drafts'
2 | export * from './groups'
3 | export * from './items'
4 | export * from './selectMode'
5 | export * from './sharing'
6 | export * from './single'
--------------------------------------------------------------------------------
/src/data/selectors/collections/selectMode.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 |
3 | export const selectMode = ({collections})=>
4 | collections.selectMode
5 |
6 | export const selectModeEnabled = ({collections}) => collections.selectMode.enabled
7 |
8 | export const makeIsSelected = ()=>createSelector(
9 | [selectMode, (state, _id)=>_id],
10 | (selectMode, _id)=>{
11 | if (!selectMode.enabled)
12 | return false;
13 |
14 | if (selectMode.ids.includes(_id))
15 | return true;
16 |
17 | return false;
18 | }
19 | )
--------------------------------------------------------------------------------
/src/data/selectors/filters/index.js:
--------------------------------------------------------------------------------
1 | export * from './items'
2 | export * from './search'
--------------------------------------------------------------------------------
/src/data/selectors/filters/items.js:
--------------------------------------------------------------------------------
1 | import { blankSpace } from '../../helpers/filters'
2 | import { createSelector } from 'reselect'
3 |
4 | //(state, spaceId)
5 | export const getFilters = ({ filters }, spaceId)=>
6 | (
7 | filters.spaces[spaceId] ? filters.spaces[spaceId] : blankSpace
8 | ).items
9 |
10 | export const getQuickFilters = createSelector(
11 | getFilters,
12 | (filters)=>
13 | filters.filter(({quick})=>quick)
14 | )
15 |
16 | export const getStatus = ({ filters }, spaceId)=>
17 | (
18 | filters.spaces[spaceId] ? filters.spaces[spaceId] : blankSpace
19 | ).status
--------------------------------------------------------------------------------
/src/data/selectors/filters/search.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { getFilters } from './'
3 |
4 | //(state, spaceId, filter) -> []
5 | export const makeFiltersSearch = ()=>createSelector(
6 | [
7 | getFilters,
8 | (state, spaceId, filter)=>filter,
9 | ],
10 | (filters, _filter)=>{
11 | const filter = String(_filter||'').trimStart().toLowerCase()
12 |
13 | if (filter)
14 | return filters.filter(({ query }) =>
15 | query.toLowerCase().includes(filter) && !query.endsWith(':')
16 | )
17 |
18 | return filters
19 | }
20 | )
--------------------------------------------------------------------------------
/src/data/selectors/oauth/connections.js:
--------------------------------------------------------------------------------
1 | export const getConnectionsClients = ({ oauth })=>
2 | oauth.connections.clients
--------------------------------------------------------------------------------
/src/data/selectors/oauth/index.js:
--------------------------------------------------------------------------------
1 | export * from './connections'
2 | export * from './my'
--------------------------------------------------------------------------------
/src/data/selectors/oauth/my.js:
--------------------------------------------------------------------------------
1 | import { blankClient } from '../../helpers/oauth'
2 | import { createSelector } from 'reselect'
3 |
4 | export const getMyClients = ({ oauth })=>
5 | oauth.my.clients
6 |
7 | export const makeClient = ()=>createSelector(
8 | [
9 | ({ oauth })=>oauth.my.clients,
10 | (state, _id)=>_id
11 | ],
12 | (clients, _id)=>
13 | clients.find(client=>client._id == _id) || blankClient
14 | )
15 |
16 | export const getTestToken = ({ oauth }, _id)=>
17 | oauth.my.testToken[_id] || null
--------------------------------------------------------------------------------
/src/data/selectors/search/index.js:
--------------------------------------------------------------------------------
1 | export * from './collections'
2 | export * from './suggestions'
3 | export * from './recent'
--------------------------------------------------------------------------------
/src/data/selectors/search/recent.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 |
3 | const emptyArray = []
4 |
5 | //(state, spaceId, filter, fullquery) -> []
6 | export const makeRecent = ()=>createSelector(
7 | [
8 | ({bookmarks={}})=>bookmarks.recent.search,
9 | (state, spaceId, filter, fullquery)=>fullquery,
10 | ],
11 | (recent, fullquery)=>{
12 | if (!fullquery) return recent
13 |
14 | const filtered = recent.filter(({query})=>query.startsWith(fullquery))
15 |
16 | //do not show only one recent that exactly the same as full query
17 | if (filtered.length == 1 && filtered[0].query == fullquery)
18 | return emptyArray
19 |
20 | return filtered
21 | }
22 | )
--------------------------------------------------------------------------------
/src/data/selectors/tags/index.js:
--------------------------------------------------------------------------------
1 | export * from './items'
2 | export * from './autocomplete'
3 | export * from './search'
--------------------------------------------------------------------------------
/src/data/selectors/tags/items.js:
--------------------------------------------------------------------------------
1 | import { blankSpace } from '../../helpers/tags'
2 |
3 | //(state, spaceId)
4 | export const getTags = ({ tags }, spaceId)=>
5 | (
6 | tags.spaces[spaceId] ? tags.spaces[spaceId] : blankSpace
7 | ).tags
--------------------------------------------------------------------------------
/src/data/utils/authStatus.js:
--------------------------------------------------------------------------------
1 | import {store} from '../index'
2 |
3 | const AuthStatus = (store, {onNotAuthorized})=>{
4 | let currentValue
5 |
6 | store.subscribe(()=>{
7 | let previousValue = currentValue
8 | currentValue = store.getState().user.status.authorized
9 |
10 | if (previousValue !== currentValue){
11 | switch(currentValue) {
12 | case 'no':
13 | if (typeof onNotAuthorized == 'function')
14 | onNotAuthorized()
15 | break;
16 | }
17 | }
18 | })
19 | }
20 |
21 | export default (options)=>AuthStatus(store, options)
--------------------------------------------------------------------------------
/src/data/utils/wrapFunc.js:
--------------------------------------------------------------------------------
1 | export default function(fn) {
2 | if (typeof fn == 'function')
3 | return function() {
4 | setTimeout(()=>fn.apply(this, arguments), 0)
5 | }
6 | else
7 | return fn
8 | }
--------------------------------------------------------------------------------
/src/local/actions/app.js:
--------------------------------------------------------------------------------
1 | import {
2 | APP_SET_THEME,
3 | APP_SET_APP_SIZE,
4 | APP_COLLECTIONS_SEARCH_RESULTS_HIDE,
5 | APP_TOGGLE_HIGHLIGHTS,
6 | APP_SET_VISITED_SPACE
7 | } from '../constants'
8 |
9 | export const setTheme = ({ app, sidebar, auto })=>({
10 | type: APP_SET_THEME,
11 | app, sidebar, auto
12 | })
13 |
14 | export const setAppSize = (appSize)=>({
15 | type: APP_SET_APP_SIZE,
16 | appSize
17 | })
18 |
19 | export const toggleCollectionsSearchResults = ()=>({
20 | type: APP_COLLECTIONS_SEARCH_RESULTS_HIDE
21 | })
22 |
23 | export const toggleHighlights = ()=>({
24 | type: APP_TOGGLE_HIGHLIGHTS
25 | })
26 |
27 | export const setVisitedSpace = ({ cId, search })=>({
28 | type: APP_SET_VISITED_SPACE,
29 | cId,
30 | search
31 | })
--------------------------------------------------------------------------------
/src/local/actions/index.js:
--------------------------------------------------------------------------------
1 | export * from './app'
2 | export * from './pause'
--------------------------------------------------------------------------------
/src/local/actions/pause.js:
--------------------------------------------------------------------------------
1 | import { PAUSE_SET } from '../constants'
2 |
3 | export const setPause = (pause)=>({
4 | type: PAUSE_SET,
5 | pause
6 | })
--------------------------------------------------------------------------------
/src/local/constants/app.js:
--------------------------------------------------------------------------------
1 | export const
2 | APP_SET_THEME = 'APP_SET_THEME',
3 | APP_SET_APP_SIZE = 'APP_SET_APP_SIZE',
4 | APP_COLLECTIONS_SEARCH_RESULTS_HIDE = 'APP_COLLECTIONS_SEARCH_RESULTS_HIDE',
5 | APP_TOGGLE_HIGHLIGHTS = 'APP_TOGGLE_HIGHLIGHTS',
6 | APP_SET_VISITED_SPACE = 'APP_SET_VISITED_SPACE'
--------------------------------------------------------------------------------
/src/local/constants/index.js:
--------------------------------------------------------------------------------
1 | export * from './app'
2 | export * from './pause'
--------------------------------------------------------------------------------
/src/local/constants/pause.js:
--------------------------------------------------------------------------------
1 | export const
2 | PAUSE_SET = 'PAUSE_SET'
--------------------------------------------------------------------------------
/src/local/reducers/pause.js:
--------------------------------------------------------------------------------
1 | import { PAUSE_SET } from '../constants'
2 |
3 | export default function(state, action) {switch (action.type) {
4 | case PAUSE_SET:
5 | return state.set('pause', action.pause)
6 | }}
--------------------------------------------------------------------------------
/src/modules/browser/eventOrder.js:
--------------------------------------------------------------------------------
1 | export const eventOrder = {
2 | add(elem) {
3 | if (!window._eventOrder)
4 | window._eventOrder = new Set([])
5 |
6 | window._eventOrder.add(elem)
7 | },
8 |
9 | delete(elem) {
10 | if (window._eventOrder)
11 | window._eventOrder.delete(elem)
12 | },
13 |
14 | isLast(elem) {
15 | if (!window._eventOrder)
16 | return true
17 |
18 | let value;
19 | for(value of window._eventOrder);
20 | return value == elem
21 | }
22 | }
--------------------------------------------------------------------------------
/src/modules/browser/index.js:
--------------------------------------------------------------------------------
1 | export * from './copyText'
2 | export * from './eventOrder'
3 | export * from './resizeObserver'
4 | export * from './scrollbarIsObtrusive'
--------------------------------------------------------------------------------
/src/modules/browser/resizeObserver.js:
--------------------------------------------------------------------------------
1 | const ResizeObserver = 'ResizeObserver' in window === false ?
2 | require('@juggle/resize-observer').ResizeObserver :
3 | window.ResizeObserver
4 |
5 | export { ResizeObserver }
--------------------------------------------------------------------------------
/src/modules/format/callback/debounce.js:
--------------------------------------------------------------------------------
1 | const timers = new Map()
2 |
3 | export default (func, ms, options={})=>{
4 | const { leading=false } = options
5 |
6 | return function(){
7 | let inProgress = false
8 | let timer = timers.get(func)
9 | if (timer){
10 | inProgress = true
11 | clearTimeout(timer)
12 | timers.delete(func)
13 | }
14 |
15 | timer = setTimeout(
16 | ()=>{
17 | func(...arguments)
18 | timers.delete(func)
19 | },
20 | leading && !inProgress ? 0 : ms
21 | )
22 | timers.set(func, timer)
23 | }
24 | }
--------------------------------------------------------------------------------
/src/modules/format/callback/use-debounce.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | // Наш хук
4 | export default function useDebounce(value, delay) {
5 | const [debouncedValue, setDebouncedValue] = useState(value);
6 |
7 | useEffect(
8 | () => {
9 | const handler = setTimeout(() => {
10 | setDebouncedValue(value);
11 | }, delay);
12 | return () => {
13 | clearTimeout(handler);
14 | };
15 | },
16 | [value]
17 | );
18 |
19 | return debouncedValue;
20 | }
--------------------------------------------------------------------------------
/src/modules/format/date/index.js:
--------------------------------------------------------------------------------
1 | export * from './parse'
2 | export * from './short'
3 | export * from './long'
4 | export * from './longTime'
5 | export * from './month'
6 | export * from './shortTime'
7 | export * from './numeric'
--------------------------------------------------------------------------------
/src/modules/format/date/parse.js:
--------------------------------------------------------------------------------
1 | import parseISO from 'date-fns/parseISO'
2 |
3 | export const parseDate = (d) => typeof d == 'string' ? parseISO(d) : d
--------------------------------------------------------------------------------
/src/modules/format/file/dataURItoFile.js:
--------------------------------------------------------------------------------
1 | const BASE64_MARKER = ';base64,'
2 |
3 | export function dataURItoFile(dataURI) {
4 | const mime = dataURI.split(BASE64_MARKER)[0].split(':')[1]
5 | const filename = 'dataURI-file-' + (new Date()).getTime() + '.' + mime.split('/')[1]
6 | const bytes = atob(dataURI.split(BASE64_MARKER)[1])
7 | const writer = new Uint8Array(new ArrayBuffer(bytes.length))
8 |
9 | for (var i=0; i < bytes.length; i++) {
10 | writer[i] = bytes.charCodeAt(i);
11 | }
12 |
13 | return new File([writer.buffer], filename, { type: mime })
14 | }
--------------------------------------------------------------------------------
/src/modules/format/file/index.js:
--------------------------------------------------------------------------------
1 | export * from './dataURItoFile'
--------------------------------------------------------------------------------
/src/modules/format/number/compact.js:
--------------------------------------------------------------------------------
1 | import t from '~t'
2 |
3 | let _format
4 | function getFormat() {
5 | if (!_format)
6 | _format = new Intl.NumberFormat(t.currentLang, { notation: 'compact' }).format
7 |
8 | return _format
9 | }
10 |
11 | export function compact(val=0) {
12 | try{ return getFormat()(val) }catch(e){}
13 | return val
14 | }
--------------------------------------------------------------------------------
/src/modules/format/number/index.js:
--------------------------------------------------------------------------------
1 | export * from './compact'
2 | export * from './fileSize'
--------------------------------------------------------------------------------
/src/modules/format/string/codeToLanguage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import t from '~t'
3 |
4 | let _format
5 | function getFormat() {
6 | if (!_format)
7 | _format = new Intl.DisplayNames(t.currentLang, {type: 'language'})
8 | return _format
9 | }
10 |
11 | export const codeToLanguage = (code) => {
12 | try{
13 | return getFormat().of(code)
14 | }catch(e){}
15 |
16 | return code
17 | }
18 |
19 | export const CodeToLanguage = React.memo(
20 | function({ date }) {
21 | return codeToLanguage(date)
22 | }
23 | )
--------------------------------------------------------------------------------
/src/modules/format/string/index.js:
--------------------------------------------------------------------------------
1 | export * from './codeToLanguage'
--------------------------------------------------------------------------------
/src/modules/format/url/extractURLs.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash-es'
2 | import isURL from 'validator/es/lib/isURL'
3 |
4 | const urlPattern = /https?:\/\/[^\s]+/gi;
5 |
6 | export function extractURLs(text) {
7 | return _.uniq(
8 | (String(text)||'').match(urlPattern) || []
9 | )
10 | .filter(url=>
11 | isURL(url, { require_protocol: true, require_tld: true })
12 | )
13 | }
--------------------------------------------------------------------------------
/src/modules/format/url/getDomain.js:
--------------------------------------------------------------------------------
1 | export function getDomain(url) {
2 | try{
3 | return new URL(url).hostname
4 | } catch(e) {}
5 | return ''
6 | }
--------------------------------------------------------------------------------
/src/modules/format/url/index.js:
--------------------------------------------------------------------------------
1 | export * from './getDomain'
2 | export * from './normalizeURL'
3 | export * from './isSPA'
4 | export * from './extractURLs'
--------------------------------------------------------------------------------
/src/modules/format/url/isSPA.js:
--------------------------------------------------------------------------------
1 | export function isSPA(url) {
2 | try {
3 | return new URL(url).hash.includes('/')
4 | } catch {
5 | return false
6 | }
7 | }
--------------------------------------------------------------------------------
/src/modules/format/url/normalizeURL.js:
--------------------------------------------------------------------------------
1 | import _normalizeURL from 'normalize-url'
2 |
3 | export function normalizeURL(str, options) {
4 | try{
5 | return _normalizeURL(str, options)
6 | } catch(e) {
7 | return str
8 | }
9 | }
--------------------------------------------------------------------------------
/src/modules/sw/component.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { target } from '~target'
3 |
4 | let registered = false
5 |
6 | export default class ServiceWorkerComponent extends React.Component {
7 | async componentDidMount() {
8 | if (process.env.NODE_ENV=='production' &&
9 | target=='web' &&
10 | 'serviceWorker' in navigator &&
11 | !registered){
12 | try{
13 | await navigator.serviceWorker.register('/sw.js')
14 | registered = true
15 | } catch(e) {
16 | console.log('Service worker registration failed:', e)
17 | }
18 | }
19 | }
20 |
21 | render() {
22 | return this.props.children
23 | }
24 | }
--------------------------------------------------------------------------------
/src/modules/vendors/sentry/index.js:
--------------------------------------------------------------------------------
1 | let Component = process.env.SENTRY_RELEASE ?
2 | require('./component').default :
3 | function({children}) { return children }
4 |
5 | export default Component
--------------------------------------------------------------------------------
/src/routes/_app/extension/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import useBookmarksChanged from './useBookmarksChanged'
3 | import useExternalLinks from './useExternalLinks'
4 |
5 | export default function AppExtension({ children }) {
6 | useBookmarksChanged()
7 | useExternalLinks()
8 |
9 | return children
10 | }
--------------------------------------------------------------------------------
/src/routes/_app/extension/useBookmarksChanged.js:
--------------------------------------------------------------------------------
1 | import { useMemo, useEffect } from 'react'
2 | import browser from '~target/extension/browser'
3 | import { useSelector } from 'react-redux'
4 | import { makeBookmarksLastChange } from '~data/selectors/bookmarks'
5 |
6 | export default function useBookmarksChanged() {
7 | const getBookmarksLastChange = useMemo(()=>makeBookmarksLastChange(), [])
8 | const bookmarksChange = useSelector(state=>getBookmarksLastChange(state))
9 |
10 | useEffect(()=>{
11 | browser.runtime.sendMessage(null, { type: 'BOOKMARKS_CHANGED' })
12 | }, [bookmarksChange])
13 | }
--------------------------------------------------------------------------------
/src/routes/_app/fallback.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function App({ children }) {
4 | return children
5 | }
--------------------------------------------------------------------------------
/src/routes/_app/index.js:
--------------------------------------------------------------------------------
1 | let Component = require('./fallback')
2 |
3 | switch (process.env.APP_TARGET) {
4 | case 'extension':
5 | Component = require('./extension')
6 | break
7 | }
8 |
9 | module.exports = Component
--------------------------------------------------------------------------------
/src/routes/_document/body.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const DocumentBody = ()=>null
4 |
5 | export default DocumentBody
--------------------------------------------------------------------------------
/src/routes/_document/html.module.css:
--------------------------------------------------------------------------------
1 | figure {
2 | margin: 0
3 | }
4 |
5 | progress::-webkit-progress-value {
6 | transition: width .3s ease-in-out;
7 | }
8 |
9 | html, body, :global(#react) {
10 | margin:0;
11 | width: 100%;
12 | height: 100%;
13 | }
14 |
15 | /* extension */
16 | @media screen and (max-width: 800px) {
17 | html:global(.extension:not(.sidepanel):not(.mobile)),
18 | html:global(.extension:not(.sidepanel):not(.mobile)) body,
19 | html:global(.extension:not(.sidepanel):not(.mobile)) :global(#react) {
20 | width: fit-content;
21 | height: fit-content;
22 | }
23 | }
24 |
25 | [hidden] {
26 | display: none !important;
27 | }
--------------------------------------------------------------------------------
/src/routes/_document/index.js:
--------------------------------------------------------------------------------
1 | import './normalize.css'
2 |
3 | import React from 'react'
4 | import { Helmet } from 'react-helmet'
5 |
6 | import HTML from './html'
7 | import Body from './body'
8 |
9 | export default class Document extends React.Component {
10 | render() {
11 | return (
12 | <>
13 |
17 |
18 |
19 |