mirror of
https://github.com/nkanaev/yarr.git
synced 2025-05-24 00:33:14 +00:00
258 lines
15 KiB
HTML
258 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>YARR</title>
|
|
<link rel="stylesheet" href="./static/stylesheets/bootstrap.min.css">
|
|
<link rel="stylesheet" href="./static/stylesheets/app.css">
|
|
<link rel="icon shortcut" href="./static/images/anchor.png">
|
|
</head>
|
|
<body>
|
|
<div class="wrapper d-flex vh-100" id="app" v-cloak>
|
|
<div class="vh-100 overflow-auto border-right flex-shrink-0" style="width: 300px">
|
|
<div class="p-2">
|
|
<div class="mb-5">
|
|
<label class="selectgroup">
|
|
<input type="radio" name="filter" value="" v-model="filterSelected">
|
|
<div class="selectgroup-label d-flex align-items-center w-100">
|
|
<span class="icon mr-2">{% inline "circle-full.svg" %}</span>
|
|
<span class="flex-fill text-left text-truncate">All</span>
|
|
<span class="counter text-right"></span>
|
|
</div>
|
|
</label>
|
|
<label class="selectgroup">
|
|
<input type="radio" name="filter" value="unread" v-model="filterSelected">
|
|
<div class="selectgroup-label d-flex align-items-center w-100">
|
|
<span class="icon mr-2">{% inline "circle.svg" %}</span>
|
|
<span class="flex-fill text-left text-truncate">Unread</span>
|
|
<span class="counter text-right">{{totalStats.unread || ''}}</span>
|
|
</div>
|
|
</label>
|
|
<label class="selectgroup">
|
|
<input type="radio" name="filter" value="starred" v-model="filterSelected">
|
|
<div class="selectgroup-label d-flex align-items-center w-100">
|
|
<span class="icon mr-2">{% inline "star.svg" %}</span>
|
|
<span class="flex-fill text-left text-truncate">Starred</span>
|
|
<span class="counter text-right">{{totalStats.starred || ''}}</span>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
<label class="selectgroup">
|
|
<input type="radio" name="feed" value="" v-model="feedSelected">
|
|
<div class="selectgroup-label d-flex align-items-center w-100">
|
|
<span class="icon mr-2">{% inline "layers.svg" %}</span>
|
|
<span class="flex-fill text-left text-truncate">All Feeds</span>
|
|
<span class="counter text-right"></span>
|
|
</div>
|
|
</label>
|
|
<div v-for="folder in foldersWithFeeds">
|
|
<label class="selectgroup mt-1">
|
|
<input type="radio" name="feed" :value="'folder:'+folder.id" v-model="feedSelected">
|
|
<div class="selectgroup-label d-flex align-items-center w-100" v-if="folder.id">
|
|
<span class="icon mr-2"
|
|
:class="{expanded: folder.is_expanded}"
|
|
@click.prevent="toggleFolderExpanded(folder)">
|
|
{% 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>
|
|
</div>
|
|
</label>
|
|
<div v-show="!folder.id || folder.is_expanded" class="mt-1" :class="{'pl-3': folder.id}">
|
|
<label class="selectgroup"
|
|
v-for="feed in folder.feeds">
|
|
<input type="radio" name="feed" :value="'feed:'+feed.id" v-model="feedSelected">
|
|
<div class="selectgroup-label d-flex align-items-center w-100">
|
|
<span class="icon mr-2">{% inline "rss.svg" %}</span>
|
|
<span class="flex-fill text-left text-truncate">{{ feed.title }}</span>
|
|
<span class="counter text-right">{{filteredFeedStats[feed.id] || ''}}</span>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="vh-100 d-flex flex-column border-right flex-shrink-0" style="width: 300px">
|
|
<div class="px-2 toolbar d-flex align-items-center">
|
|
<button class="toolbar-item">
|
|
<span class="icon">{% inline "search.svg" %}</span>
|
|
</button>
|
|
<input class="d-block toolbar-search" type="" v-model="itemSearch">
|
|
<div class="flex-grow-1"> </div>
|
|
<button class="toolbar-item" @click="markItemsRead()" v-if="filterSelected != 'starred'">
|
|
<span class="icon">{% inline "check.svg" %}</span>
|
|
</button>
|
|
</div>
|
|
<div class="p-2 overflow-auto border-top" v-scroll="loadMoreItems">
|
|
<label v-for="item in items" :key="item.id"
|
|
class="selectgroup"
|
|
:class="{'text-muted' : filterSelected=='all' && item.status=='read',
|
|
'text-primary': filterSelected=='all' && item.status=='starred'}">
|
|
<input type="radio" name="item" :value="item.id" v-model="itemSelected">
|
|
<div class="selectgroup-label d-flex flex-column">
|
|
<div style="line-height: 1.2; opacity: .6;" class="d-flex">
|
|
<small class="flex-fill text-truncate mr-1">{{feedsById[item.feed_id].title}}</small>
|
|
<small class="flex-shrink-0"><relative-time :val="item.date"/></small>
|
|
</div>
|
|
<span>{{item.title}}</span>
|
|
</div>
|
|
</label>
|
|
<button class="btn btn-link btn-block loading my-3" v-if="itemsPage.cur < itemsPage.num"></button>
|
|
</div>
|
|
</div>
|
|
<div class="vh-100 d-flex flex-column w-100">
|
|
<div class="toolbar pl-2 d-flex align-items-center">
|
|
<div v-if="itemSelected">
|
|
<button class="toolbar-item" @click="toggleItemStarred(itemSelectedDetails)">
|
|
<span class="icon" v-if="itemSelectedDetails.status=='starred'" >{% inline "star-full.svg" %}</span>
|
|
<span class="icon" v-else-if="itemSelectedDetails.status!='starred'" >{% inline "star.svg" %}</span>
|
|
</button>
|
|
<button class="toolbar-item"
|
|
:disabled="itemSelectedDetails.status=='starred'"
|
|
@click="toggleItemRead(itemSelectedDetails)">
|
|
<span class="icon" v-if="itemSelectedDetails.status=='unread'">{% inline "circle-full.svg" %}</span>
|
|
<span class="icon" v-if="itemSelectedDetails.status!='unread'">{% inline "circle.svg" %}</span>
|
|
</button>
|
|
<a class="toolbar-item" :href="itemSelectedDetails.link" target="_blank">
|
|
<span class="icon">{% inline "external-link.svg" %}</span>
|
|
</a>
|
|
<button class="toolbar-item" @click="getReadable(itemSelectedDetails)">
|
|
<span class="icon">{% inline "book-open.svg" %}</span>
|
|
</button>
|
|
</div>
|
|
<div class="flex-grow-1"> </div>
|
|
<button class="toolbar-item" v-b-modal.settings-modal>
|
|
<span class="icon">{% inline "settings.svg" %}</span>
|
|
</button>
|
|
</div>
|
|
<div v-if="itemSelected" class="px-4 pt-3 pb-5 border-top overflow-auto">
|
|
<h3>{{itemSelectedDetails.title}}</h3>
|
|
<div class="text-muted">
|
|
<div>{{ feedsById[itemSelectedDetails.feed_id].title }}</div>
|
|
<time>{{ formatDate(itemSelectedDetails.date) }}</time>
|
|
</div>
|
|
<hr>
|
|
<div class="content">
|
|
<div v-html="itemSelectedReadability" v-if="itemSelectedReadability"></div>
|
|
<div v-html="itemSelectedDetails.content" v-else-if="itemSelectedDetails.content"></div>
|
|
<div v-html="itemSelectedDetails.description" v-else-if="itemSelectedDetails.description"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<b-modal id="settings-modal" hide-header hide-footer lazy>
|
|
<ul class="nav nav-tabs mx-n3 px-3 mb-3 mt-n3 pt-2 bg-light rounded-top">
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="#" :class="{active: settings=='create'}" @click.prevent="settings='create'">
|
|
<span class="icon mr-1">{% inline "plus-square.svg" %}</span>
|
|
New Feed
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="#" :class="{active: settings=='manage'}" @click.prevent="settings='manage'">
|
|
<span class="icon mr-1">{% inline "list.svg" %}</span>
|
|
Manage Feeds
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="#" :class="{active: settings=='import'}" @click.prevent="settings='import'">
|
|
<span class="icon mr-1">{% inline "upload.svg" %}</span>
|
|
Import/Export
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
<div v-if="settings=='create'">
|
|
<form action="" @submit.prevent="createFeed(event)">
|
|
<label for="feed-url">URL</label>
|
|
<input id="feed-url" name="url" type="url" class="form-control" required autocomplete="off">
|
|
<label for="feed-folder" class="mt-3">Folder</label>
|
|
<select class="form-control" id="feed-folder" name="folder_id">
|
|
<option value=""></option>
|
|
<option :value="folder.id" v-for="folder in folders">{{ folder.title }}</option>
|
|
</select>
|
|
<button class="btn btn-block btn-default mt-3" :class="{loading: loading.newfeed}" type="submit">Add</button>
|
|
</form>
|
|
</div>
|
|
<div v-else-if="settings=='manage'">
|
|
<div v-for="folder in foldersWithFeeds" class="mb-4" :key="folder.id">
|
|
<div class="d-flex align-items-center">
|
|
<div class="w-100" v-if="folder.id"><b>{{ folder.title }}</b></div>
|
|
<div class="flex-shrink-0" v-if="folder.id">
|
|
<b-dropdown right no-caret lazy="true" variant="link" class="settings-dropdown" toggle-class="text-decoration-none">
|
|
<template v-slot:button-content>
|
|
<span class="icon">{% inline "more-vertical.svg" %}</span>
|
|
</template>
|
|
<b-dropdown-item @click.prevent="renameFolder(folder)">Rename</b-dropdown-item>
|
|
<b-dropdown-divider></b-dropdown-divider>
|
|
<b-dropdown-item class="dropdown-danger"
|
|
@click.prevent="deleteFolder(folder)">
|
|
Delete
|
|
</b-dropdown-item>
|
|
</b-dropdown>
|
|
</div>
|
|
</div>
|
|
<div v-for="feed in folder.feeds" class="d-flex align-items-center" :key="feed.id">
|
|
<div class="w-100">
|
|
{{ feed.title }}
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<b-dropdown right no-caret lazy="true" variant="link" class="settings-dropdown" toggle-class="text-decoration-none">
|
|
<template v-slot:button-content>
|
|
<span class="icon">{% inline "more-vertical.svg" %}</span>
|
|
</template>
|
|
<b-dropdown-item @click.prevent="renameFeed(feed)">Rename</b-dropdown-item>
|
|
<b-dropdown-divider v-if="folders.length"></b-dropdown-divider>
|
|
<b-dropdown-header v-if="folders.length">Move to...</b-dropdown-header>
|
|
<b-dropdown-item
|
|
v-if="folder.id != feed.folder_id"
|
|
v-for="folder in folders"
|
|
@click.prevent="moveFeed(feed, folder)">
|
|
{{ folder.title }}
|
|
</b-dropdown-item>
|
|
<b-dropdown-item
|
|
@click.prevent="moveFeed(feed, null)" v-if="feed.folder_id">
|
|
Uncategorized
|
|
</b-dropdown-item>
|
|
<b-dropdown-divider></b-dropdown-divider>
|
|
<b-dropdown-item class="dropdown-danger"
|
|
@click.prevent="deleteFeed(feed)">
|
|
Delete
|
|
</b-dropdown-item>
|
|
</b-dropdown>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-5">
|
|
<form action="" @submit.prevent="createFolder(event)">
|
|
<label for="settings-new-folder">New Folder</label>
|
|
<div class="input-group">
|
|
<input id="settings-new-folder" type="text" class="form-control" name="title" required autocomplete="off">
|
|
<div class="input-group-append">
|
|
<button class="btn btn-default" type="submit">Add</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="settings=='import'">
|
|
<form class="d-inline-block m" id="opml-import-form" enctype="multipart/form-data" style="position: relative;">
|
|
<input type="file"
|
|
id="opml-import"
|
|
@change="importOPML"
|
|
name="opml"
|
|
style="opacity: 0; width: 1px; height: 0; position: absolute;">
|
|
<label class="btn btn-default m-0" for="opml-import">Import subscriptions</label>
|
|
</form>
|
|
<a class="btn btn-default" href="/opml/export">Export subscriptions</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="./static/javascripts/vue.min.js"></script>
|
|
<script src="./static/javascripts/popper.min.js"></script>
|
|
<!-- <script src="./static/javascripts/bootstrap.min.js"></script> -->
|
|
<script src="./static/javascripts/bootstrap-vue.min.js"></script>
|
|
<script src="./static/javascripts/Readability.js"></script>
|
|
<script src="./static/javascripts/api.js"></script>
|
|
<script src="./static/javascripts/app.js"></script>
|
|
</body>
|
|
</html>
|