25 Commits
v1.0 ... v1.1

Author SHA1 Message Date
Nazar Kanaev
bcab24ebfa v1.1 2020-10-05 22:27:09 +01:00
Nazar Kanaev
dd058e1637 autogenerate version 2020-10-05 21:59:53 +01:00
nkanaev
63f624251d Update readme.md 2020-10-05 16:16:04 +01:00
Nazar Kanaev
05ee99b4d9 update build.yml 2020-10-05 15:39:19 +01:00
Nazar Kanaev
d5cc9149e3 do not refresh items after feed is deselected 2020-10-03 21:33:35 +01:00
Nazar Kanaev
eb2029132c oopsies 2020-10-03 20:54:51 +01:00
Nazar Kanaev
492e77fd25 done 2020-10-03 13:01:22 +01:00
Nazar Kanaev
b8aa15d554 tablet & mobile layouts 2020-10-03 12:57:30 +01:00
Nazar Kanaev
ee0b440b7b bug fix 2020-10-02 22:14:01 +01:00
Nazar Kanaev
72da9df5ac space 2020-10-02 21:15:55 +01:00
Nazar Kanaev
9872bf84f0 truncate choice text 2020-10-02 20:09:03 +01:00
Nazar Kanaev
6222761dd3 fix favicon fetch bug 2020-10-02 16:49:34 +01:00
Nazar Kanaev
71cc8929ad remove log 2020-10-02 15:18:57 +01:00
Nazar Kanaev
8007853a9a tweak timeouts 2020-10-02 15:17:59 +01:00
Nazar Kanaev
ffc506371c default build 2020-10-02 12:38:33 +01:00
Nazar Kanaev
00fed5e0cf show addr in logs 2020-10-02 12:37:50 +01:00
Nazar Kanaev
6937c349f0 rename assets go file 2020-10-01 21:57:34 +01:00
Nazar Kanaev
bac136603b move icons to platform package 2020-10-01 21:53:48 +01:00
Nazar Kanaev
693b5bcb8d handle server error 2020-09-25 19:23:38 +01:00
Nazar Kanaev
9c7d95f632 tweak 2020-09-25 11:59:08 +01:00
Nazar Kanaev
b8adb3fa2f fluid width 2020-09-25 11:32:15 +01:00
nkanaev
5652b240dc Update hacking.md 2020-09-25 00:05:57 +01:00
nkanaev
c1c0ffab01 Update hacking.md 2020-09-24 23:55:23 +01:00
Nazar Kanaev
349a8980f2 hacking 2020-09-24 23:54:01 +01:00
nkanaev
0bd117804d Update build.yml 2020-09-24 23:28:38 +01:00
19 changed files with 353 additions and 139 deletions

View File

@@ -2,7 +2,7 @@ name: build
on:
push:
branches: [master]
tags: [v*]
jobs:
build_macos:
@@ -11,23 +11,42 @@ jobs:
steps:
- {name: "Checkout", uses: actions/checkout@v2}
- {name: "Checkout gofeed", uses: actions/checkout@v2, with: {repository: nkanaev/gofeed, path: gofeed}}
- name: Cache Go Modules
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: "Build"
run: |
go version
make build_macos
cd _output/macos && zip -r yarr-macos.zip yarr.app
- {name: "Upload", uses: actions/upload-artifact@v2, with: {name: macos, path: _output/macos/yarr-macos.zip}}
run: make build_macos
- name: Upload
uses: actions/upload-artifact@v2
with:
name: macos
path: _output/macos/yarr.app
build_windows:
name: Build for Windows
runs-on: windows-2019
steps:
- {name: "Checkout", uses: actions/checkout@v2}
- {name: "Checkout gofeed", uses: actions/checkout@v2, with: {repository: nkanaev/gofeed, path: gofeed}}
- name: Cache Go Modules
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: "Build"
run: |
go version
make build_windows
- {name: "Upload", uses: actions/upload-artifact@v2, with: {name: windows, path: _output/windows/yarr.exe}}
run: make build_windows
- name: Upload
uses: actions/upload-artifact@v2
with:
name: windows
path: _output/windows/yarr.exe
build_linux:
name: Build for Linux
runs-on: ubuntu-18.04
@@ -35,9 +54,73 @@ jobs:
- {name: "Checkout", uses: actions/checkout@v2}
- {name: "Checkout gofeed", uses: actions/checkout@v2, with: {repository: nkanaev/gofeed, path: gofeed}}
- {name: "Setup Go", uses: actions/setup-go@v2, with: {go-version: '^1.14'}}
- name: Cache Go Modules
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: "Build"
run: make build_linux
- name: Upload
uses: actions/upload-artifact@v2
with:
name: linux
path: _output/linux/yarr
create_release:
name: Create Release
runs-on: ubuntu-latest
needs: [build_macos, build_windows, build_linux]
steps:
- name: Create Release
uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
prerelease: true
- name: Download Artifacts
uses: actions/download-artifact@v2
with:
path: .
- name: Preparation
run: |
go version
make build_linux
cd _output/linux && zip -r yarr-linux.zip yarr
- {name: "Upload", uses: actions/upload-artifact@v2, with: {name: linux, path: _output/linux/yarr-linux.zip}}
ls -R
chmod u+x macos/Contents/MacOS/yarr
chmod u+x linux/yarr
mv macos yarr.app && zip -r yarr-macos.zip yarr.app
mv windows/yarr.exe . && zip yarr-windows.zip yarr.exe
mv linux/yarr . && zip yarr-linux.zip yarr
- name: Upload MacOS
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./yarr-macos.zip
asset_name: yarr-${{ github.ref }}-macos64.zip
asset_content_type: application/zip
- name: Upload Windows
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./yarr-windows.zip
asset_name: yarr-${{ github.ref }}-windows32.zip
asset_content_type: application/zip
- name: Upload Linux
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./yarr-linux.zip
asset_name: yarr-${{ github.ref }}-linux32.zip
asset_content_type: application/zip

3
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/server/assets_bundle.go
/server/assets.go
/gofeed
/_output
/yarr
*.db
*.syso
versioninfo.rc

View File

@@ -1,26 +0,0 @@
1 VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "080904E4"
BEGIN
VALUE "CompanyName", "Old MacDonald's Farm"
VALUE "FileDescription", "Yet another RSS reader"
VALUE "FileVersion", "1.0"
VALUE "InternalName", "yarr"
VALUE "LegalCopyright", "nkanaev"
VALUE "OriginalFilename", "yarr.exe"
VALUE "ProductName", "yarr"
VALUE "ProductVersion", "1.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x809, 1252
END
END
1 ICON "icon.ico"

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-left"><polyline points="15 18 9 12 15 6"></polyline></svg>

After

Width:  |  Height:  |  Size: 270 B

View File

@@ -6,11 +6,12 @@
<link rel="stylesheet" href="./static/stylesheets/bootstrap.min.css">
<link rel="stylesheet" href="./static/stylesheets/app.css">
<link rel="icon shortcut" href="./static/graphicarts/anchor.png">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body class="theme-light">
<div class="wrapper d-flex vh-100" id="app" v-cloak>
<div id="app" class="d-flex" :class="{'feed-selected': feedSelected !== null, 'item-selected': itemSelected !== null}" v-cloak>
<!-- feed list -->
<div class="vh-100 position-relative d-flex flex-column border-right flex-shrink-0" :style="{width: feedListWidth+'px'}">
<div id="col-feed-list" class="vh-100 position-relative d-flex flex-column border-right flex-shrink-0" :style="{width: feedListWidth+'px'}">
<drag :width="feedListWidth" @resize="resizeFeedList"></drag>
<div class="p-2 toolbar d-flex align-items-center">
<div class="icon mx-2">{% inline "anchor.svg" %}</div>
@@ -92,7 +93,7 @@
<span class="flex-fill text-left text-truncate" v-if="filterSelected=='unread'">All Unread</span>
<span class="flex-fill text-left text-truncate" v-if="filterSelected=='starred'">All Starred</span>
<span class="flex-fill text-left text-truncate" v-if="filterSelected==''">All Feeds</span>
<span class="counter text-right">{{filteredTotalStats}}</span>
<span class="counter text-right">{{ filteredTotalStats }}</span>
</div>
</label>
<div v-for="folder in foldersWithFeeds">
@@ -108,7 +109,7 @@
{% inline "chevron-right.svg" %}
</span>
<span class="flex-fill text-left text-truncate">{{ folder.title }}</span>
<span class="counter text-right">{{filteredFolderStats[folder.id] || ''}}</span>
<span class="counter text-right">{{ filteredFolderStats[folder.id] || '' }}</span>
</div>
</label>
<div v-show="!folder.id || folder.is_expanded" class="mt-1" :class="{'pl-3': folder.id}">
@@ -122,7 +123,7 @@
<span class="icon mr-2" v-if="!feed.has_icon">{% inline "rss.svg" %}</span>
<span class="icon mr-2" v-else><img v-lazy="'/api/feeds/'+feed.id+'/icon'" alt=""></span>
<span class="flex-fill text-left text-truncate">{{ feed.title }}</span>
<span class="counter text-right">{{filteredFeedStats[feed.id] || ''}}</span>
<span class="counter text-right">{{ filteredFeedStats[feed.id] || '' }}</span>
</div>
</label>
</div>
@@ -130,13 +131,18 @@
</div>
<div class="p-2 toolbar d-flex align-items-center border-top flex-shrink-0" v-if="loading.feeds">
<span class="icon loading mx-2"></span>
<span class="text-truncate cursor-default noselect">Refreshing ({{loading.feeds}} left)</span>
<span class="text-truncate cursor-default noselect">Refreshing ({{ loading.feeds }} left)</span>
</div>
</div>
<!-- item list -->
<div class="vh-100 position-relative d-flex flex-column border-right flex-shrink-0" :style="{width: itemListWidth+'px'}">
<div id="col-item-list" class="vh-100 position-relative d-flex flex-column border-right flex-shrink-0" :style="{width: itemListWidth+'px'}">
<drag :width="itemListWidth" @resize="resizeItemList"></drag>
<div class="px-2 toolbar d-flex align-items-center">
<button class="toolbar-item mr-2 d-block d-md-none"
@click="feedSelected = null"
v-b-tooltip.hover.bottom="'Show Feeds'">
<span class="icon">{% inline "chevron-left.svg" %}</span>
</button>
<div class="input-icon flex-grow-1">
<span class="icon">{% inline "search.svg" %}</span>
<input class="d-block toolbar-search" type="" v-model="itemSearch">
@@ -159,18 +165,18 @@
<span class="icon icon-small mr-1" v-if="item.status=='starred'">{% inline "star-full.svg" %}</span>
</transition>
<small class="flex-fill text-truncate mr-1">
{{feedsById[item.feed_id].title}}
{{ feedsById[item.feed_id].title }}
</small>
<small class="flex-shrink-0"><relative-time :val="item.date"/></small>
</div>
<div>{{item.title || 'untitled'}}</div>
<div>{{ item.title || 'untitled' }}</div>
</div>
</label>
<button class="btn btn-link btn-block loading my-3" v-if="itemsPage.cur < itemsPage.num"></button>
</div>
</div>
<!-- item show -->
<div class="vh-100 d-flex flex-column w-100" style="min-width: 0;">
<div id="col-item" class="vh-100 d-flex flex-column w-100" style="min-width: 0;">
<div class="toolbar px-2 d-flex align-items-center" v-if="itemSelected">
<button class="toolbar-item"
@click="toggleItemStarred(itemSelectedDetails)"
@@ -244,7 +250,7 @@
ref="content"
class="content px-4 pt-3 pb-5 border-top overflow-auto"
:style="{'font-family': theme.font, 'font-size': theme.size + 'rem'}">
<h1><b>{{itemSelectedDetails.title}}</b></h1>
<h1><b>{{ itemSelectedDetails.title }}</b></h1>
<div class="text-muted">
<div>{{ feedsById[itemSelectedDetails.feed_id].title }}</div>
<time>{{ formatDate(itemSelectedDetails.date) }}</time>
@@ -278,8 +284,8 @@
<label class="selectgroup" v-for="choice in feedNewChoice">
<input type="radio" name="feedToAdd" :value="choice.url" v-model="feedNewChoiceSelected">
<div class="selectgroup-label">
<div>{{ choice.title }}</div>
<div :class="{light: choice.title}">{{ choice.url }}</div>
<div class="text-truncate">{{ choice.title }}</div>
<div class="text-truncate" :class="{light: choice.title}">{{ choice.url }}</div>
</div>
</label>
</div>
@@ -299,7 +305,7 @@
<template v-slot:button-content>
<span class="icon">{% inline "more-vertical.svg" %}</span>
</template>
<b-dropdown-header>{{folder.title}}</b-dropdown-header>
<b-dropdown-header>{{ folder.title }}</b-dropdown-header>
<b-dropdown-item @click.prevent="renameFolder(folder)">Rename</b-dropdown-item>
<b-dropdown-divider></b-dropdown-divider>
<b-dropdown-item class="dropdown-danger"
@@ -320,7 +326,7 @@
<template v-slot:button-content>
<span class="icon">{% inline "more-vertical.svg" %}</span>
</template>
<b-dropdown-header>{{feed.title}}</b-dropdown-header>
<b-dropdown-header>{{ feed.title }}</b-dropdown-header>
<b-dropdown-item :href="feed.link" target="_blank" v-if="feed.link">Visit Website</b-dropdown-item>
<b-dropdown-divider v-if="feed.link"></b-dropdown-divider>
<b-dropdown-item @click.prevent="renameFeed(feed)">Rename</b-dropdown-item>

View File

@@ -100,7 +100,7 @@ Vue.component('relative-time', {
'interval': null,
}
},
template: '<time :datetime="val">{{formatted}}</time>',
template: '<time :datetime="val">{{ formatted }}</time>',
mounted: function() {
this.interval = setInterval(function() {
this.formatted = dateRepr(this.date)
@@ -126,11 +126,11 @@ var vm = new Vue({
},
data: function() {
return {
'filterSelected': null,
'filterSelected': undefined,
'folders': [],
'feeds': [],
'feedSelected': null,
'feedListWidth': null,
'feedSelected': undefined,
'feedListWidth': undefined,
'feedNewChoice': [],
'feedNewChoiceSelected': '',
'items': [],
@@ -142,8 +142,8 @@ var vm = new Vue({
'itemSelectedDetails': {},
'itemSelectedReadability': '',
'itemSearch': '',
'itemSortNewestFirst': null,
'itemListWidth': null,
'itemSortNewestFirst': undefined,
'itemListWidth': undefined,
'filteredFeedStats': {},
'filteredFolderStats': {},
@@ -229,13 +229,13 @@ var vm = new Vue({
}, 500),
},
'filterSelected': function(newVal, oldVal) {
if (oldVal === null) return // do nothing, initial setup
if (oldVal === undefined) return // do nothing, initial setup
api.settings.update({filter: newVal}).then(this.refreshItems.bind(this))
this.itemSelected = null
this.computeStats()
},
'feedSelected': function(newVal, oldVal) {
if (oldVal === null) return // do nothing, initial setup
if (oldVal === undefined) return // do nothing, initial setup
api.settings.update({feed: newVal}).then(this.refreshItems.bind(this))
this.itemSelected = null
if (this.$refs.itemlist) this.$refs.itemlist.scrollTop = 0
@@ -259,15 +259,15 @@ var vm = new Vue({
this.refreshItems()
}, 500),
'itemSortNewestFirst': function(newVal, oldVal) {
if (oldVal === null) return
if (oldVal === undefined) return // do nothing, initial setup
api.settings.update({sort_newest_first: newVal}).then(this.refreshItems.bind(this))
},
'feedListWidth': debounce(function(newVal, oldVal) {
if (oldVal === null) return
if (oldVal === undefined) return // do nothing, initial setup
api.settings.update({feed_list_width: newVal})
}, 1000),
'itemListWidth': debounce(function(newVal, oldVal) {
if (oldVal === null) return
if (oldVal === undefined) return // do nothing, initial setup
api.settings.update({item_list_width: newVal})
}, 1000),
},
@@ -318,9 +318,14 @@ var vm = new Vue({
})
},
refreshItems: function() {
if (this.feedSelected === null) {
vm.items = []
vm.itemsPage = {'cur': 1, 'num': 1}
return
}
var query = this.getItemsQuery()
this.loading.items = true
api.items.list(query).then(function(data) {
return api.items.list(query).then(function(data) {
vm.items = data.list
vm.itemsPage = data.page
vm.loading.items = false
@@ -420,10 +425,17 @@ var vm = new Vue({
deleteFeed: function(feed) {
if (confirm('Are you sure you want to delete ' + feed.title + '?')) {
api.feeds.delete(feed.id).then(function() {
if (vm.feedSelected === 'feed:'+feed.id) {
vm.items = []
vm.feedSelected = ''
// note: if item list contains delete feed's entries, refresh it first.
for (var i = 0; i < vm.items.length; i++) {
if (vm.items[i].feed_id == feed.id) {
vm.refreshItems().then(function() {
vm.refreshStats()
vm.refreshFeeds()
})
return
}
}
vm.refreshStats()
vm.refreshFeeds()
})

View File

@@ -6,12 +6,6 @@ body {
font-size: 15px !important;
}
.wrapper {
max-width: 1440px;
margin: 0 auto;
overflow-x: hidden;
}
/* bootstrap customizations */
.btn-link {
@@ -418,6 +412,7 @@ select.form-control:not([multiple]):not([size]) {
.content pre {
color: inherit;
overflow-x: scroll;
}
.content a {
@@ -430,10 +425,6 @@ select.form-control:not([multiple]):not([size]) {
padding-left: 1rem;
}
.content pre {
overflow-x: scroll;
}
.content h1 {
font-size: 1.8rem;
}
@@ -519,3 +510,71 @@ a,
opacity: 0;
margin: 0 !important;
}
/* responsive layout
tablet:
none selected: show feed list & item list
feed selected: show feed list & item list
item selected: show item
mobile:
none selected: show feed list
feed selected: show item list
item selected: show item
*/
@media (min-width: 768px) and (max-width: 991.98px) {
#app #col-feed-list {
width: 35% !important;
}
#app #col-item-list {
width: 65% !important;
border-right-width: 0 !important;
}
#app #col-item {
display: none !important;
}
#app.item-selected #col-feed-list {
display: none !important;
}
#app.item-selected #col-item-list {
display: none !important;
}
#app.item-selected #col-item {
display: flex !important;
}
}
@media (max-width: 767.98px) {
#app #col-feed-list {
width: 100% !important;
border-right-width: 0 !important;
}
#app #col-item-list {
width: 100% !important;
display: none !important;
border-right-width: 0 !important;
}
#app #col-item {
width: 100% !important;
display: none !important;
}
#app.feed-selected #col-feed-list {
display: none !important;
}
#app.feed-selected #col-item-list {
display: flex !important;
}
#app.item-selected #col-feed-list {
display: none !important;
}
#app.item-selected #col-item-list {
display: none !important;
}
#app.item-selected #col-item {
display: flex !important;
}
}

37
hacking.md Normal file
View File

@@ -0,0 +1,37 @@
# hacking
If you have any questions/suggestions/proposals,
you can reach out the author via e-mail (nkanaev@live.com)
or mastodon (https://fosstodon.org/@nkanaev).
## build
Install `Go >= 1.14` and `gcc`. Get the source code:
```sh
git clone https://github.com/nkanaev/yarr.git
git clone https://github.com/nkanaev/gofeed.git
mv gofeed yarr
cd yarr
```
Then:
```sh
# create a binary for the host os
make build_macos # -> _output/macos/yarr.app
make build_linux # -> _output/linux/yarr
make build_windows # -> _output/windows/yarr.exe
# ... or run locally (for testing & hacking)
go run main.go # starts a server at http://localhost:7070
```
## plans
- feeds health checker
- Fever API support
## code of conduct
Be excellent to each other. Party on, dudes!

View File

@@ -11,7 +11,7 @@ import (
"path/filepath"
)
var Version string = "v0.0"
var Version string = "0.0"
var GitHash string = "unknown"
func main() {
@@ -23,7 +23,7 @@ func main() {
flag.Parse()
if ver {
fmt.Printf("%s (%s)\n", Version, GitHash)
fmt.Printf("v%s (%s)\n", Version, GitHash)
return
}
@@ -48,5 +48,6 @@ func main() {
}
srv := server.New(db, logger, addr)
logger.Printf("starting server at http://%s", addr)
platform.Start(srv)
}

View File

@@ -1,4 +1,4 @@
VERSION=v1.0
VERSION=1.1
GITHASH=$(shell git rev-parse --short=8 HEAD)
ASSETS = assets/javascripts/* assets/stylesheets/* assets/graphicarts/* assets/index.html
@@ -9,10 +9,14 @@ GO_LDFLAGS := $(GO_LDFLAGS) -X 'main.Version=$(VERSION)' -X 'main.GitHash=$(GITH
default: bundle
server/assets_bundle.go: $(ASSETS)
server/assets.go: $(ASSETS)
go run scripts/bundle_assets.go >/dev/null
bundle: server/assets_bundle.go
bundle: server/assets.go
build_default: bundle
mkdir -p _output
go build -tags "sqlite_foreign_keys release" -ldflags="$(GO_LDFLAGS)" -o _output/yarr main.go
build_macos: bundle
set GOOS=darwin
@@ -20,7 +24,7 @@ build_macos: bundle
mkdir -p _output/macos
go build -tags "sqlite_foreign_keys release macos" -ldflags="$(GO_LDFLAGS)" -o _output/macos/yarr main.go
cp artwork/icon.png _output/macos/icon.png
go run scripts/package_macos.go _output/macos
go run scripts/package_macos.go -outdir _output/macos -version "$(VERSION)"
build_linux: bundle
set GOOS=linux
@@ -32,5 +36,6 @@ build_windows: bundle
set GOOS=windows
set GOARCH=386
mkdir -p _output/windows
go run scripts/generate_versioninfo.go -version "$(VERSION)" -outfile artwork/versioninfo.rc
windres -i artwork/versioninfo.rc -O coff -o platform/versioninfo.syso
go build -tags "sqlite_foreign_keys release windows" -ldflags="$(GO_LDFLAGS) -H windowsgui" -o _output/windows/yarr.exe main.go

View File

@@ -10,7 +10,7 @@ import (
func Start(s *server.Handler) {
systrayOnReady := func() {
systray.SetIcon(server.Icon)
systray.SetIcon(Icon)
menuOpen := systray.AddMenuItem("Open", "")
systray.AddSeparator()

View File

@@ -1,8 +1,8 @@
// +build !windows
// +build macos
// File generated by 2goarray v0.1.0 (http://github.com/cratonica/2goarray)
package server
package platform
var Icon []byte = []byte{
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,

View File

@@ -2,7 +2,7 @@
// File generated by 2goarray v0.1.0 (http://github.com/cratonica/2goarray)
package server
package platform
var Icon []byte = []byte{
0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,

View File

@@ -1,4 +1,4 @@
# yarr (beta)
# yarr
yet another rss reader.
@@ -8,36 +8,10 @@ yet another rss reader.
The goal of the project is to provide a desktop application accessible via web browser.
Longer-term plans include a self-hosted solution for individuals.
There are plans to add support for mobile & tablet resolutions.
Support for 3rd-party applications (via Fever API) is being considered.
## build
Install `Go >= 1.14` and `gcc`, then run:
```sh
git clone https://github.com/nkanaev/yarr.git
git clone https://github.com/nkanaev/gofeed.git
mv gofeed yarr
cd yarr && make build_macos
```
## plans
- test across 3 platforms (macos, linux, windows)
- prebuilt binaries
- GUI-less mode (no tray icon)
- feeds health checker
- mobile & tablet layout
- parameters (`--[no]-gui`, `--addr`, ...)
- Fever API support
- keyboard navigation
[download](https://github.com/nkanaev/yarr/releases/latest)
## credits
[Feather](http://feathericons.com/) for icons.
## code of conduct
Be excellent to each other. Party on, dudes!

View File

@@ -92,5 +92,5 @@ func main() {
var buf bytes.Buffer
template := template.Must(template.New("code").Parse(code_template))
template.Execute(&buf, assets)
ioutil.WriteFile("server/assets_bundle.go", buf.Bytes(), 0644)
ioutil.WriteFile("server/assets.go", buf.Bytes(), 0644)
}

View File

@@ -0,0 +1,48 @@
package main
import (
"io/ioutil"
"flag"
"strings"
)
var rsrc = `1 VERSIONINFO
FILEVERSION {VERSION_COMMA},0,0
PRODUCTVERSION {VERSION_COMMA},0,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "080904E4"
BEGIN
VALUE "CompanyName", "Old MacDonald's Farm"
VALUE "FileDescription", "Yet another RSS reader"
VALUE "FileVersion", "{VERSION}"
VALUE "InternalName", "yarr"
VALUE "LegalCopyright", "nkanaev"
VALUE "OriginalFilename", "yarr.exe"
VALUE "ProductName", "yarr"
VALUE "ProductVersion", "{VERSION}"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x809, 1252
END
END
1 ICON "icon.ico"
`
func main() {
var version, outfile string
flag.StringVar(&version, "version", "0.0", "")
flag.StringVar(&outfile, "outfile", "versioninfo.rc", "")
flag.Parse()
version_comma := strings.ReplaceAll(version, ".", ",")
out := strings.ReplaceAll(rsrc, "{VERSION}", version)
out = strings.ReplaceAll(out, "{VERSION_COMMA}", version_comma)
ioutil.WriteFile(outfile, []byte(out), 0644)
}

View File

@@ -1,13 +1,15 @@
package main
import (
"os"
"path"
"flag"
"fmt"
"io/ioutil"
"os/exec"
"strconv"
"log"
"os"
"os/exec"
"path"
"strconv"
"strings"
)
var plist = `<?xml version="1.0" encoding="UTF-8"?>
@@ -21,7 +23,7 @@ var plist = `<?xml version="1.0" encoding="UTF-8"?>
<key>CFBundleIdentifier</key>
<string>nkanaev.yarr</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<string>VERSION</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleExecutable</key>
@@ -35,11 +37,6 @@ var plist = `<?xml version="1.0" encoding="UTF-8"?>
<key>NSHighResolutionCapable</key>
<string>True</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
<key>LSUIElement</key>
@@ -59,7 +56,11 @@ func run(cmd ...string) {
}
func main() {
outdir := os.Args[1]
var version, outdir string
flag.StringVar(&version, "version", "0.0", "")
flag.StringVar(&outdir, "outdir", "", "")
flag.Parse()
outfile := "yarr"
binDir := path.Join(outdir, "yarr.app", "Contents/MacOS")
@@ -74,7 +75,7 @@ func main() {
f, _ := ioutil.ReadFile(path.Join(outdir, outfile))
ioutil.WriteFile(path.Join(binDir, outfile), f, 0755)
ioutil.WriteFile(plistFile, []byte(plist), 0644)
ioutil.WriteFile(plistFile, []byte(strings.Replace(plist, "VERSION", version, 1)), 0644)
ioutil.WriteFile(pkginfoFile, []byte("APPL????"), 0644)
iconFile := path.Join(outdir, "icon.png")

View File

@@ -8,6 +8,7 @@ import (
"github.com/mmcdole/gofeed"
"github.com/nkanaev/yarr/storage"
"io/ioutil"
"net"
"net/http"
"net/url"
"time"
@@ -157,13 +158,16 @@ func findFavicon(websiteUrl, feedUrl string) (*[]byte, error) {
}
if len(websiteUrl) != 0 {
base, err := url.Parse(websiteUrl)
if err != nil {
return nil, err
}
res, err := defaultClient.get(websiteUrl)
if err != nil {
return nil, err
}
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
base, err := url.Parse(websiteUrl)
if err != nil {
return nil, err
}
@@ -258,10 +262,16 @@ func listItems(f storage.Feed) ([]storage.Item, error) {
}
func init() {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.DisableKeepAlives = true
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
}).DialContext,
DisableKeepAlives: true,
TLSHandshakeTimeout: time.Second * 10,
}
httpClient := &http.Client{
Timeout: time.Second * 5,
Timeout: time.Second * 30,
Transport: transport,
}
defaultClient = &Client{

View File

@@ -32,7 +32,10 @@ func New(db *storage.Storage, logger *log.Logger, addr string) *Handler {
func (h *Handler) Start() {
h.startJobs()
s := &http.Server{Addr: h.Addr, Handler: h}
s.ListenAndServe()
err := s.ListenAndServe()
if err != http.ErrServerClosed {
h.log.Fatal(err)
}
}
func (h Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
@@ -75,13 +78,12 @@ func (h *Handler) startJobs() {
h.db.CreateItems(items)
syncSearch()
if !feed.HasIcon {
h.log.Println("searching favicon for:", feed.Link, feed.FeedLink)
icon, err := findFavicon(feed.Link, feed.FeedLink)
if icon != nil {
h.db.UpdateFeedIcon(feed.Id, icon)
}
if err != nil {
h.log.Print(err)
h.log.Printf("Failed to search favicon for %s (%s): %s", feed.Link, feed.FeedLink, err)
}
}
case <- delTicker.C: