mirror of
https://github.com/nkanaev/yarr.git
synced 2025-09-15 10:50:15 +00:00
Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
bcab24ebfa | ||
|
dd058e1637 | ||
|
63f624251d | ||
|
05ee99b4d9 | ||
|
d5cc9149e3 | ||
|
eb2029132c | ||
|
492e77fd25 | ||
|
b8aa15d554 | ||
|
ee0b440b7b | ||
|
72da9df5ac | ||
|
9872bf84f0 | ||
|
6222761dd3 | ||
|
71cc8929ad | ||
|
8007853a9a | ||
|
ffc506371c | ||
|
00fed5e0cf | ||
|
6937c349f0 | ||
|
bac136603b | ||
|
693b5bcb8d | ||
|
9c7d95f632 | ||
|
b8adb3fa2f | ||
|
5652b240dc | ||
|
c1c0ffab01 | ||
|
349a8980f2 | ||
|
0bd117804d |
111
.github/workflows/build.yml
vendored
111
.github/workflows/build.yml
vendored
@@ -2,7 +2,7 @@ name: build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
tags: [v*]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_macos:
|
build_macos:
|
||||||
@@ -11,23 +11,42 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- {name: "Checkout", uses: actions/checkout@v2}
|
- {name: "Checkout", uses: actions/checkout@v2}
|
||||||
- {name: "Checkout gofeed", uses: actions/checkout@v2, with: {repository: nkanaev/gofeed, path: gofeed}}
|
- {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"
|
- name: "Build"
|
||||||
run: |
|
run: make build_macos
|
||||||
go version
|
- name: Upload
|
||||||
make build_macos
|
uses: actions/upload-artifact@v2
|
||||||
cd _output/macos && zip -r yarr-macos.zip yarr.app
|
with:
|
||||||
- {name: "Upload", uses: actions/upload-artifact@v2, with: {name: macos, path: _output/macos/yarr-macos.zip}}
|
name: macos
|
||||||
|
path: _output/macos/yarr.app
|
||||||
|
|
||||||
build_windows:
|
build_windows:
|
||||||
name: Build for Windows
|
name: Build for Windows
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
steps:
|
steps:
|
||||||
- {name: "Checkout", uses: actions/checkout@v2}
|
- {name: "Checkout", uses: actions/checkout@v2}
|
||||||
- {name: "Checkout gofeed", uses: actions/checkout@v2, with: {repository: nkanaev/gofeed, path: gofeed}}
|
- {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"
|
- name: "Build"
|
||||||
run: |
|
run: make build_windows
|
||||||
go version
|
- name: Upload
|
||||||
make build_windows
|
uses: actions/upload-artifact@v2
|
||||||
- {name: "Upload", uses: actions/upload-artifact@v2, with: {name: windows, path: _output/windows/yarr.exe}}
|
with:
|
||||||
|
name: windows
|
||||||
|
path: _output/windows/yarr.exe
|
||||||
|
|
||||||
build_linux:
|
build_linux:
|
||||||
name: Build for Linux
|
name: Build for Linux
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-18.04
|
||||||
@@ -35,9 +54,73 @@ jobs:
|
|||||||
- {name: "Checkout", uses: actions/checkout@v2}
|
- {name: "Checkout", uses: actions/checkout@v2}
|
||||||
- {name: "Checkout gofeed", uses: actions/checkout@v2, with: {repository: nkanaev/gofeed, path: gofeed}}
|
- {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: "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"
|
- 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: |
|
run: |
|
||||||
go version
|
ls -R
|
||||||
make build_linux
|
chmod u+x macos/Contents/MacOS/yarr
|
||||||
cd _output/linux && zip -r yarr-linux.zip yarr
|
chmod u+x linux/yarr
|
||||||
- {name: "Upload", uses: actions/upload-artifact@v2, with: {name: linux, path: _output/linux/yarr-linux.zip}}
|
|
||||||
|
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
3
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
/server/assets_bundle.go
|
/server/assets.go
|
||||||
/gofeed
|
/gofeed
|
||||||
/_output
|
/_output
|
||||||
/yarr
|
/yarr
|
||||||
*.db
|
*.db
|
||||||
*.syso
|
*.syso
|
||||||
|
versioninfo.rc
|
||||||
|
@@ -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"
|
|
||||||
|
|
1
assets/graphicarts/chevron-left.svg
Normal file
1
assets/graphicarts/chevron-left.svg
Normal 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 |
@@ -6,11 +6,12 @@
|
|||||||
<link rel="stylesheet" href="./static/stylesheets/bootstrap.min.css">
|
<link rel="stylesheet" href="./static/stylesheets/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="./static/stylesheets/app.css">
|
<link rel="stylesheet" href="./static/stylesheets/app.css">
|
||||||
<link rel="icon shortcut" href="./static/graphicarts/anchor.png">
|
<link rel="icon shortcut" href="./static/graphicarts/anchor.png">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
</head>
|
</head>
|
||||||
<body class="theme-light">
|
<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 -->
|
<!-- 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>
|
<drag :width="feedListWidth" @resize="resizeFeedList"></drag>
|
||||||
<div class="p-2 toolbar d-flex align-items-center">
|
<div class="p-2 toolbar d-flex align-items-center">
|
||||||
<div class="icon mx-2">{% inline "anchor.svg" %}</div>
|
<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=='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=='starred'">All Starred</span>
|
||||||
<span class="flex-fill text-left text-truncate" v-if="filterSelected==''">All Feeds</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>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div v-for="folder in foldersWithFeeds">
|
<div v-for="folder in foldersWithFeeds">
|
||||||
@@ -108,7 +109,7 @@
|
|||||||
{% inline "chevron-right.svg" %}
|
{% inline "chevron-right.svg" %}
|
||||||
</span>
|
</span>
|
||||||
<span class="flex-fill text-left text-truncate">{{ folder.title }}</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>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div v-show="!folder.id || folder.is_expanded" class="mt-1" :class="{'pl-3': folder.id}">
|
<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-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="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="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>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,13 +131,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="p-2 toolbar d-flex align-items-center border-top flex-shrink-0" v-if="loading.feeds">
|
<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="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>
|
||||||
</div>
|
</div>
|
||||||
<!-- item list -->
|
<!-- 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>
|
<drag :width="itemListWidth" @resize="resizeItemList"></drag>
|
||||||
<div class="px-2 toolbar d-flex align-items-center">
|
<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">
|
<div class="input-icon flex-grow-1">
|
||||||
<span class="icon">{% inline "search.svg" %}</span>
|
<span class="icon">{% inline "search.svg" %}</span>
|
||||||
<input class="d-block toolbar-search" type="" v-model="itemSearch">
|
<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>
|
<span class="icon icon-small mr-1" v-if="item.status=='starred'">{% inline "star-full.svg" %}</span>
|
||||||
</transition>
|
</transition>
|
||||||
<small class="flex-fill text-truncate mr-1">
|
<small class="flex-fill text-truncate mr-1">
|
||||||
{{feedsById[item.feed_id].title}}
|
{{ feedsById[item.feed_id].title }}
|
||||||
</small>
|
</small>
|
||||||
<small class="flex-shrink-0"><relative-time :val="item.date"/></small>
|
<small class="flex-shrink-0"><relative-time :val="item.date"/></small>
|
||||||
</div>
|
</div>
|
||||||
<div>{{item.title || 'untitled'}}</div>
|
<div>{{ item.title || 'untitled' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<button class="btn btn-link btn-block loading my-3" v-if="itemsPage.cur < itemsPage.num"></button>
|
<button class="btn btn-link btn-block loading my-3" v-if="itemsPage.cur < itemsPage.num"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- item show -->
|
<!-- 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">
|
<div class="toolbar px-2 d-flex align-items-center" v-if="itemSelected">
|
||||||
<button class="toolbar-item"
|
<button class="toolbar-item"
|
||||||
@click="toggleItemStarred(itemSelectedDetails)"
|
@click="toggleItemStarred(itemSelectedDetails)"
|
||||||
@@ -244,7 +250,7 @@
|
|||||||
ref="content"
|
ref="content"
|
||||||
class="content px-4 pt-3 pb-5 border-top overflow-auto"
|
class="content px-4 pt-3 pb-5 border-top overflow-auto"
|
||||||
:style="{'font-family': theme.font, 'font-size': theme.size + 'rem'}">
|
: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 class="text-muted">
|
||||||
<div>{{ feedsById[itemSelectedDetails.feed_id].title }}</div>
|
<div>{{ feedsById[itemSelectedDetails.feed_id].title }}</div>
|
||||||
<time>{{ formatDate(itemSelectedDetails.date) }}</time>
|
<time>{{ formatDate(itemSelectedDetails.date) }}</time>
|
||||||
@@ -278,8 +284,8 @@
|
|||||||
<label class="selectgroup" v-for="choice in feedNewChoice">
|
<label class="selectgroup" v-for="choice in feedNewChoice">
|
||||||
<input type="radio" name="feedToAdd" :value="choice.url" v-model="feedNewChoiceSelected">
|
<input type="radio" name="feedToAdd" :value="choice.url" v-model="feedNewChoiceSelected">
|
||||||
<div class="selectgroup-label">
|
<div class="selectgroup-label">
|
||||||
<div>{{ choice.title }}</div>
|
<div class="text-truncate">{{ choice.title }}</div>
|
||||||
<div :class="{light: choice.title}">{{ choice.url }}</div>
|
<div class="text-truncate" :class="{light: choice.title}">{{ choice.url }}</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -299,7 +305,7 @@
|
|||||||
<template v-slot:button-content>
|
<template v-slot:button-content>
|
||||||
<span class="icon">{% inline "more-vertical.svg" %}</span>
|
<span class="icon">{% inline "more-vertical.svg" %}</span>
|
||||||
</template>
|
</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-item @click.prevent="renameFolder(folder)">Rename</b-dropdown-item>
|
||||||
<b-dropdown-divider></b-dropdown-divider>
|
<b-dropdown-divider></b-dropdown-divider>
|
||||||
<b-dropdown-item class="dropdown-danger"
|
<b-dropdown-item class="dropdown-danger"
|
||||||
@@ -320,7 +326,7 @@
|
|||||||
<template v-slot:button-content>
|
<template v-slot:button-content>
|
||||||
<span class="icon">{% inline "more-vertical.svg" %}</span>
|
<span class="icon">{% inline "more-vertical.svg" %}</span>
|
||||||
</template>
|
</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-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-divider v-if="feed.link"></b-dropdown-divider>
|
||||||
<b-dropdown-item @click.prevent="renameFeed(feed)">Rename</b-dropdown-item>
|
<b-dropdown-item @click.prevent="renameFeed(feed)">Rename</b-dropdown-item>
|
||||||
|
@@ -100,7 +100,7 @@ Vue.component('relative-time', {
|
|||||||
'interval': null,
|
'interval': null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
template: '<time :datetime="val">{{formatted}}</time>',
|
template: '<time :datetime="val">{{ formatted }}</time>',
|
||||||
mounted: function() {
|
mounted: function() {
|
||||||
this.interval = setInterval(function() {
|
this.interval = setInterval(function() {
|
||||||
this.formatted = dateRepr(this.date)
|
this.formatted = dateRepr(this.date)
|
||||||
@@ -126,11 +126,11 @@ var vm = new Vue({
|
|||||||
},
|
},
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
'filterSelected': null,
|
'filterSelected': undefined,
|
||||||
'folders': [],
|
'folders': [],
|
||||||
'feeds': [],
|
'feeds': [],
|
||||||
'feedSelected': null,
|
'feedSelected': undefined,
|
||||||
'feedListWidth': null,
|
'feedListWidth': undefined,
|
||||||
'feedNewChoice': [],
|
'feedNewChoice': [],
|
||||||
'feedNewChoiceSelected': '',
|
'feedNewChoiceSelected': '',
|
||||||
'items': [],
|
'items': [],
|
||||||
@@ -142,8 +142,8 @@ var vm = new Vue({
|
|||||||
'itemSelectedDetails': {},
|
'itemSelectedDetails': {},
|
||||||
'itemSelectedReadability': '',
|
'itemSelectedReadability': '',
|
||||||
'itemSearch': '',
|
'itemSearch': '',
|
||||||
'itemSortNewestFirst': null,
|
'itemSortNewestFirst': undefined,
|
||||||
'itemListWidth': null,
|
'itemListWidth': undefined,
|
||||||
|
|
||||||
'filteredFeedStats': {},
|
'filteredFeedStats': {},
|
||||||
'filteredFolderStats': {},
|
'filteredFolderStats': {},
|
||||||
@@ -229,13 +229,13 @@ var vm = new Vue({
|
|||||||
}, 500),
|
}, 500),
|
||||||
},
|
},
|
||||||
'filterSelected': function(newVal, oldVal) {
|
'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))
|
api.settings.update({filter: newVal}).then(this.refreshItems.bind(this))
|
||||||
this.itemSelected = null
|
this.itemSelected = null
|
||||||
this.computeStats()
|
this.computeStats()
|
||||||
},
|
},
|
||||||
'feedSelected': function(newVal, oldVal) {
|
'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))
|
api.settings.update({feed: newVal}).then(this.refreshItems.bind(this))
|
||||||
this.itemSelected = null
|
this.itemSelected = null
|
||||||
if (this.$refs.itemlist) this.$refs.itemlist.scrollTop = 0
|
if (this.$refs.itemlist) this.$refs.itemlist.scrollTop = 0
|
||||||
@@ -259,15 +259,15 @@ var vm = new Vue({
|
|||||||
this.refreshItems()
|
this.refreshItems()
|
||||||
}, 500),
|
}, 500),
|
||||||
'itemSortNewestFirst': function(newVal, oldVal) {
|
'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))
|
api.settings.update({sort_newest_first: newVal}).then(this.refreshItems.bind(this))
|
||||||
},
|
},
|
||||||
'feedListWidth': debounce(function(newVal, oldVal) {
|
'feedListWidth': debounce(function(newVal, oldVal) {
|
||||||
if (oldVal === null) return
|
if (oldVal === undefined) return // do nothing, initial setup
|
||||||
api.settings.update({feed_list_width: newVal})
|
api.settings.update({feed_list_width: newVal})
|
||||||
}, 1000),
|
}, 1000),
|
||||||
'itemListWidth': debounce(function(newVal, oldVal) {
|
'itemListWidth': debounce(function(newVal, oldVal) {
|
||||||
if (oldVal === null) return
|
if (oldVal === undefined) return // do nothing, initial setup
|
||||||
api.settings.update({item_list_width: newVal})
|
api.settings.update({item_list_width: newVal})
|
||||||
}, 1000),
|
}, 1000),
|
||||||
},
|
},
|
||||||
@@ -318,9 +318,14 @@ var vm = new Vue({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
refreshItems: function() {
|
refreshItems: function() {
|
||||||
|
if (this.feedSelected === null) {
|
||||||
|
vm.items = []
|
||||||
|
vm.itemsPage = {'cur': 1, 'num': 1}
|
||||||
|
return
|
||||||
|
}
|
||||||
var query = this.getItemsQuery()
|
var query = this.getItemsQuery()
|
||||||
this.loading.items = true
|
this.loading.items = true
|
||||||
api.items.list(query).then(function(data) {
|
return api.items.list(query).then(function(data) {
|
||||||
vm.items = data.list
|
vm.items = data.list
|
||||||
vm.itemsPage = data.page
|
vm.itemsPage = data.page
|
||||||
vm.loading.items = false
|
vm.loading.items = false
|
||||||
@@ -420,10 +425,17 @@ var vm = new Vue({
|
|||||||
deleteFeed: function(feed) {
|
deleteFeed: function(feed) {
|
||||||
if (confirm('Are you sure you want to delete ' + feed.title + '?')) {
|
if (confirm('Are you sure you want to delete ' + feed.title + '?')) {
|
||||||
api.feeds.delete(feed.id).then(function() {
|
api.feeds.delete(feed.id).then(function() {
|
||||||
if (vm.feedSelected === 'feed:'+feed.id) {
|
// note: if item list contains delete feed's entries, refresh it first.
|
||||||
vm.items = []
|
for (var i = 0; i < vm.items.length; i++) {
|
||||||
vm.feedSelected = ''
|
if (vm.items[i].feed_id == feed.id) {
|
||||||
|
vm.refreshItems().then(function() {
|
||||||
|
vm.refreshStats()
|
||||||
|
vm.refreshFeeds()
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.refreshStats()
|
vm.refreshStats()
|
||||||
vm.refreshFeeds()
|
vm.refreshFeeds()
|
||||||
})
|
})
|
||||||
|
@@ -6,12 +6,6 @@ body {
|
|||||||
font-size: 15px !important;
|
font-size: 15px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
max-width: 1440px;
|
|
||||||
margin: 0 auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* bootstrap customizations */
|
/* bootstrap customizations */
|
||||||
|
|
||||||
.btn-link {
|
.btn-link {
|
||||||
@@ -418,6 +412,7 @@ select.form-control:not([multiple]):not([size]) {
|
|||||||
|
|
||||||
.content pre {
|
.content pre {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
overflow-x: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content a {
|
.content a {
|
||||||
@@ -430,10 +425,6 @@ select.form-control:not([multiple]):not([size]) {
|
|||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content pre {
|
|
||||||
overflow-x: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content h1 {
|
.content h1 {
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
}
|
}
|
||||||
@@ -519,3 +510,71 @@ a,
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
margin: 0 !important;
|
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
37
hacking.md
Normal 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!
|
5
main.go
5
main.go
@@ -11,7 +11,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version string = "v0.0"
|
var Version string = "0.0"
|
||||||
var GitHash string = "unknown"
|
var GitHash string = "unknown"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -23,7 +23,7 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if ver {
|
if ver {
|
||||||
fmt.Printf("%s (%s)\n", Version, GitHash)
|
fmt.Printf("v%s (%s)\n", Version, GitHash)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,5 +48,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
srv := server.New(db, logger, addr)
|
srv := server.New(db, logger, addr)
|
||||||
|
logger.Printf("starting server at http://%s", addr)
|
||||||
platform.Start(srv)
|
platform.Start(srv)
|
||||||
}
|
}
|
||||||
|
13
makefile
13
makefile
@@ -1,4 +1,4 @@
|
|||||||
VERSION=v1.0
|
VERSION=1.1
|
||||||
GITHASH=$(shell git rev-parse --short=8 HEAD)
|
GITHASH=$(shell git rev-parse --short=8 HEAD)
|
||||||
|
|
||||||
ASSETS = assets/javascripts/* assets/stylesheets/* assets/graphicarts/* assets/index.html
|
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
|
default: bundle
|
||||||
|
|
||||||
server/assets_bundle.go: $(ASSETS)
|
server/assets.go: $(ASSETS)
|
||||||
go run scripts/bundle_assets.go >/dev/null
|
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
|
build_macos: bundle
|
||||||
set GOOS=darwin
|
set GOOS=darwin
|
||||||
@@ -20,7 +24,7 @@ build_macos: bundle
|
|||||||
mkdir -p _output/macos
|
mkdir -p _output/macos
|
||||||
go build -tags "sqlite_foreign_keys release macos" -ldflags="$(GO_LDFLAGS)" -o _output/macos/yarr main.go
|
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
|
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
|
build_linux: bundle
|
||||||
set GOOS=linux
|
set GOOS=linux
|
||||||
@@ -32,5 +36,6 @@ build_windows: bundle
|
|||||||
set GOOS=windows
|
set GOOS=windows
|
||||||
set GOARCH=386
|
set GOARCH=386
|
||||||
mkdir -p _output/windows
|
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
|
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
|
go build -tags "sqlite_foreign_keys release windows" -ldflags="$(GO_LDFLAGS) -H windowsgui" -o _output/windows/yarr.exe main.go
|
||||||
|
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func Start(s *server.Handler) {
|
func Start(s *server.Handler) {
|
||||||
systrayOnReady := func() {
|
systrayOnReady := func() {
|
||||||
systray.SetIcon(server.Icon)
|
systray.SetIcon(Icon)
|
||||||
|
|
||||||
menuOpen := systray.AddMenuItem("Open", "")
|
menuOpen := systray.AddMenuItem("Open", "")
|
||||||
systray.AddSeparator()
|
systray.AddSeparator()
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
// +build !windows
|
// +build macos
|
||||||
|
|
||||||
// File generated by 2goarray v0.1.0 (http://github.com/cratonica/2goarray)
|
// File generated by 2goarray v0.1.0 (http://github.com/cratonica/2goarray)
|
||||||
|
|
||||||
package server
|
package platform
|
||||||
|
|
||||||
var Icon []byte = []byte{
|
var Icon []byte = []byte{
|
||||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
// File generated by 2goarray v0.1.0 (http://github.com/cratonica/2goarray)
|
// File generated by 2goarray v0.1.0 (http://github.com/cratonica/2goarray)
|
||||||
|
|
||||||
package server
|
package platform
|
||||||
|
|
||||||
var Icon []byte = []byte{
|
var Icon []byte = []byte{
|
||||||
0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
30
readme.md
30
readme.md
@@ -1,4 +1,4 @@
|
|||||||
# yarr (beta)
|
# yarr
|
||||||
|
|
||||||
yet another rss reader.
|
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.
|
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.
|
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.
|
Support for 3rd-party applications (via Fever API) is being considered.
|
||||||
|
|
||||||
## build
|
[download](https://github.com/nkanaev/yarr/releases/latest)
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
## credits
|
## credits
|
||||||
|
|
||||||
[Feather](http://feathericons.com/) for icons.
|
[Feather](http://feathericons.com/) for icons.
|
||||||
|
|
||||||
## code of conduct
|
|
||||||
|
|
||||||
Be excellent to each other. Party on, dudes!
|
|
||||||
|
@@ -92,5 +92,5 @@ func main() {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
template := template.Must(template.New("code").Parse(code_template))
|
template := template.Must(template.New("code").Parse(code_template))
|
||||||
template.Execute(&buf, assets)
|
template.Execute(&buf, assets)
|
||||||
ioutil.WriteFile("server/assets_bundle.go", buf.Bytes(), 0644)
|
ioutil.WriteFile("server/assets.go", buf.Bytes(), 0644)
|
||||||
}
|
}
|
||||||
|
48
scripts/generate_versioninfo.go
Normal file
48
scripts/generate_versioninfo.go
Normal 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)
|
||||||
|
}
|
@@ -1,13 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"flag"
|
||||||
"path"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var plist = `<?xml version="1.0" encoding="UTF-8"?>
|
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>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>nkanaev.yarr</string>
|
<string>nkanaev.yarr</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>VERSION</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
@@ -35,11 +37,6 @@ var plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<string>True</string>
|
<string>True</string>
|
||||||
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>10.13</string>
|
<string>10.13</string>
|
||||||
<key>LSUIElement</key>
|
<key>LSUIElement</key>
|
||||||
@@ -59,7 +56,11 @@ func run(cmd ...string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
outdir := os.Args[1]
|
var version, outdir string
|
||||||
|
flag.StringVar(&version, "version", "0.0", "")
|
||||||
|
flag.StringVar(&outdir, "outdir", "", "")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
outfile := "yarr"
|
outfile := "yarr"
|
||||||
|
|
||||||
binDir := path.Join(outdir, "yarr.app", "Contents/MacOS")
|
binDir := path.Join(outdir, "yarr.app", "Contents/MacOS")
|
||||||
@@ -74,7 +75,7 @@ func main() {
|
|||||||
f, _ := ioutil.ReadFile(path.Join(outdir, outfile))
|
f, _ := ioutil.ReadFile(path.Join(outdir, outfile))
|
||||||
ioutil.WriteFile(path.Join(binDir, outfile), f, 0755)
|
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)
|
ioutil.WriteFile(pkginfoFile, []byte("APPL????"), 0644)
|
||||||
|
|
||||||
iconFile := path.Join(outdir, "icon.png")
|
iconFile := path.Join(outdir, "icon.png")
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/mmcdole/gofeed"
|
"github.com/mmcdole/gofeed"
|
||||||
"github.com/nkanaev/yarr/storage"
|
"github.com/nkanaev/yarr/storage"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
@@ -157,13 +158,16 @@ func findFavicon(websiteUrl, feedUrl string) (*[]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(websiteUrl) != 0 {
|
if len(websiteUrl) != 0 {
|
||||||
|
base, err := url.Parse(websiteUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
res, err := defaultClient.get(websiteUrl)
|
res, err := defaultClient.get(websiteUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
doc, err := goquery.NewDocumentFromReader(res.Body)
|
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||||
base, err := url.Parse(websiteUrl)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -258,10 +262,16 @@ func listItems(f storage.Feed) ([]storage.Item, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
transport := &http.Transport{
|
||||||
transport.DisableKeepAlives = true
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}).DialContext,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
TLSHandshakeTimeout: time.Second * 10,
|
||||||
|
}
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 30,
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
}
|
}
|
||||||
defaultClient = &Client{
|
defaultClient = &Client{
|
||||||
|
@@ -32,7 +32,10 @@ func New(db *storage.Storage, logger *log.Logger, addr string) *Handler {
|
|||||||
func (h *Handler) Start() {
|
func (h *Handler) Start() {
|
||||||
h.startJobs()
|
h.startJobs()
|
||||||
s := &http.Server{Addr: h.Addr, Handler: h}
|
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) {
|
func (h Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
@@ -75,13 +78,12 @@ func (h *Handler) startJobs() {
|
|||||||
h.db.CreateItems(items)
|
h.db.CreateItems(items)
|
||||||
syncSearch()
|
syncSearch()
|
||||||
if !feed.HasIcon {
|
if !feed.HasIcon {
|
||||||
h.log.Println("searching favicon for:", feed.Link, feed.FeedLink)
|
|
||||||
icon, err := findFavicon(feed.Link, feed.FeedLink)
|
icon, err := findFavicon(feed.Link, feed.FeedLink)
|
||||||
if icon != nil {
|
if icon != nil {
|
||||||
h.db.UpdateFeedIcon(feed.Id, icon)
|
h.db.UpdateFeedIcon(feed.Id, icon)
|
||||||
}
|
}
|
||||||
if err != nil {
|
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:
|
case <- delTicker.C:
|
||||||
|
Reference in New Issue
Block a user