Add Pinboard integration
This commit is contained in:
parent
2f1367a8d4
commit
2356ddad28
28 changed files with 550 additions and 49 deletions
45
integration/pinboard/pinboard.go
Normal file
45
integration/pinboard/pinboard.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package pinboard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/miniflux/miniflux2/reader/http"
|
||||
)
|
||||
|
||||
// Client represents a Pinboard token.
|
||||
type Client struct {
|
||||
authToken string
|
||||
}
|
||||
|
||||
// AddBookmark sends a link to Pinboard.
|
||||
func (c *Client) AddBookmark(link, title, tags string, markAsUnread bool) error {
|
||||
toRead := "no"
|
||||
if markAsUnread {
|
||||
toRead = "yes"
|
||||
}
|
||||
|
||||
values := url.Values{}
|
||||
values.Add("auth_token", c.authToken)
|
||||
values.Add("url", link)
|
||||
values.Add("description", title)
|
||||
values.Add("tags", tags)
|
||||
values.Add("toread", toRead)
|
||||
|
||||
client := http.NewClient("https://api.pinboard.in/v1/posts/add?" + values.Encode())
|
||||
response, err := client.Get()
|
||||
if response.HasServerFailure() {
|
||||
return fmt.Errorf("unable to send bookmark to pinboard, status=%d", response.StatusCode)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewClient returns a new Pinboard client.
|
||||
func NewClient(authToken string) *Client {
|
||||
return &Client{authToken: authToken}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-02 16:12:47.287568844 -0800 PST m=+0.033078160
|
||||
// 2017-12-02 19:31:37.042505019 -0800 PST m=+0.027446145
|
||||
|
||||
package locale
|
||||
|
||||
|
@ -153,12 +153,19 @@ var translations = map[string]string{
|
|||
"Invalid theme.": "Le thème est invalide.",
|
||||
"Entry Sorting": "Ordre des éléments",
|
||||
"Older entries first": "Ancien éléments en premier",
|
||||
"Recent entries first": "Éléments récents en premier"
|
||||
"Recent entries first": "Éléments récents en premier",
|
||||
"Saving...": "Enregistrement...",
|
||||
"Done!": "Terminé !",
|
||||
"Save this article": "Sauvegarder cet article",
|
||||
"Mark bookmark as unread": "Marquer le lien comme non lu",
|
||||
"Pinboard Tags": "Libellés de Pinboard",
|
||||
"Pinboard API Token": "Jeton de sécurité de l'API de Pinboard",
|
||||
"Enable Pinboard": "Activer Pinboard"
|
||||
}
|
||||
`,
|
||||
}
|
||||
|
||||
var translationsChecksums = map[string]string{
|
||||
"en_US": "6fe95384260941e8a5a3c695a655a932e0a8a6a572c1e45cb2b1ae8baa01b897",
|
||||
"fr_FR": "5c054c06fa687f05fd4f6041b002207fe1fe304d6c0c0d094b8caa61a5071ba5",
|
||||
"fr_FR": "fce31dfc3b8d45ee1c5d0c7aca4449553a8228a2428491e5cf5cf9e507dddb31",
|
||||
}
|
||||
|
|
|
@ -137,5 +137,12 @@
|
|||
"Invalid theme.": "Le thème est invalide.",
|
||||
"Entry Sorting": "Ordre des éléments",
|
||||
"Older entries first": "Ancien éléments en premier",
|
||||
"Recent entries first": "Éléments récents en premier"
|
||||
"Recent entries first": "Éléments récents en premier",
|
||||
"Saving...": "Enregistrement...",
|
||||
"Done!": "Terminé !",
|
||||
"Save this article": "Sauvegarder cet article",
|
||||
"Mark bookmark as unread": "Marquer le lien comme non lu",
|
||||
"Pinboard Tags": "Libellés de Pinboard",
|
||||
"Pinboard API Token": "Jeton de sécurité de l'API de Pinboard",
|
||||
"Enable Pinboard": "Activer Pinboard"
|
||||
}
|
||||
|
|
14
model/integration.go
Normal file
14
model/integration.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package model
|
||||
|
||||
// Integration represents user integration settings.
|
||||
type Integration struct {
|
||||
UserID int64
|
||||
PinboardEnabled bool
|
||||
PinboardToken string
|
||||
PinboardTags string
|
||||
PinboardMarkAsUnread bool
|
||||
}
|
|
@ -23,22 +23,17 @@ type Client struct {
|
|||
url string
|
||||
etagHeader string
|
||||
lastModifiedHeader string
|
||||
username string
|
||||
password string
|
||||
Insecure bool
|
||||
}
|
||||
|
||||
// Get execute a GET HTTP request.
|
||||
func (h *Client) Get() (*Response, error) {
|
||||
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[HttpClient:Get] url=%s", h.url))
|
||||
u, _ := url.Parse(h.url)
|
||||
func (c *Client) Get() (*Response, error) {
|
||||
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[HttpClient:Get] url=%s", c.url))
|
||||
|
||||
req := &http.Request{
|
||||
URL: u,
|
||||
Method: http.MethodGet,
|
||||
Header: h.buildHeaders(),
|
||||
}
|
||||
|
||||
client := h.buildClient()
|
||||
resp, err := client.Do(req)
|
||||
client := c.buildClient()
|
||||
resp, err := client.Do(c.buildRequest())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -53,7 +48,7 @@ func (h *Client) Get() (*Response, error) {
|
|||
}
|
||||
|
||||
log.Println("[HttpClient:Get]",
|
||||
"OriginalURL:", h.url,
|
||||
"OriginalURL:", c.url,
|
||||
"StatusCode:", response.StatusCode,
|
||||
"ETag:", response.ETag,
|
||||
"LastModified:", response.LastModified,
|
||||
|
@ -63,9 +58,24 @@ func (h *Client) Get() (*Response, error) {
|
|||
return response, err
|
||||
}
|
||||
|
||||
func (h *Client) buildClient() http.Client {
|
||||
func (c *Client) buildRequest() *http.Request {
|
||||
link, _ := url.Parse(c.url)
|
||||
request := &http.Request{
|
||||
URL: link,
|
||||
Method: http.MethodGet,
|
||||
Header: c.buildHeaders(),
|
||||
}
|
||||
|
||||
if c.username != "" && c.password != "" {
|
||||
request.SetBasicAuth(c.username, c.password)
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
func (c *Client) buildClient() http.Client {
|
||||
client := http.Client{Timeout: time.Duration(requestTimeout * time.Second)}
|
||||
if h.Insecure {
|
||||
if c.Insecure {
|
||||
client.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
@ -74,16 +84,16 @@ func (h *Client) buildClient() http.Client {
|
|||
return client
|
||||
}
|
||||
|
||||
func (h *Client) buildHeaders() http.Header {
|
||||
func (c *Client) buildHeaders() http.Header {
|
||||
headers := make(http.Header)
|
||||
headers.Add("User-Agent", userAgent)
|
||||
|
||||
if h.etagHeader != "" {
|
||||
headers.Add("If-None-Match", h.etagHeader)
|
||||
if c.etagHeader != "" {
|
||||
headers.Add("If-None-Match", c.etagHeader)
|
||||
}
|
||||
|
||||
if h.lastModifiedHeader != "" {
|
||||
headers.Add("If-Modified-Since", h.lastModifiedHeader)
|
||||
if c.lastModifiedHeader != "" {
|
||||
headers.Add("If-Modified-Since", c.lastModifiedHeader)
|
||||
}
|
||||
|
||||
return headers
|
||||
|
@ -94,6 +104,11 @@ func NewClient(url string) *Client {
|
|||
return &Client{url: url, Insecure: false}
|
||||
}
|
||||
|
||||
// NewClientWithCredentials returns a new HTTP client that require authentication.
|
||||
func NewClientWithCredentials(url, username, password string) *Client {
|
||||
return &Client{url: url, Insecure: false, username: username, password: password}
|
||||
}
|
||||
|
||||
// NewClientWithCacheHeaders returns a new HTTP client that send cache headers.
|
||||
func NewClientWithCacheHeaders(url, etagHeader, lastModifiedHeader string) *Client {
|
||||
return &Client{url: url, etagHeader: etagHeader, lastModifiedHeader: lastModifiedHeader, Insecure: false}
|
||||
|
|
|
@ -91,6 +91,7 @@ func getRoutes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Han
|
|||
router.Handle("/category/{categoryID}/entry/{entryID}", uiHandler.Use(uiController.ShowCategoryEntry)).Name("categoryEntry").Methods("GET")
|
||||
|
||||
router.Handle("/entry/status", uiHandler.Use(uiController.UpdateEntriesStatus)).Name("updateEntriesStatus").Methods("POST")
|
||||
router.Handle("/entry/save/{entryID}", uiHandler.Use(uiController.SaveEntry)).Name("saveEntry").Methods("POST")
|
||||
|
||||
router.Handle("/categories", uiHandler.Use(uiController.ShowCategories)).Name("categories").Methods("GET")
|
||||
router.Handle("/category/create", uiHandler.Use(uiController.CreateCategory)).Name("createCategory").Methods("GET")
|
||||
|
@ -117,6 +118,7 @@ func getRoutes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Han
|
|||
|
||||
router.Handle("/bookmarklet", uiHandler.Use(uiController.Bookmarklet)).Name("bookmarklet").Methods("GET")
|
||||
router.Handle("/integrations", uiHandler.Use(uiController.ShowIntegrations)).Name("integrations").Methods("GET")
|
||||
router.Handle("/integration", uiHandler.Use(uiController.UpdateIntegration)).Name("updateIntegration").Methods("POST")
|
||||
|
||||
router.Handle("/sessions", uiHandler.Use(uiController.ShowSessions)).Name("sessions").Methods("GET")
|
||||
router.Handle("/sessions/{sessionID}/remove", uiHandler.Use(uiController.RemoveSession)).Name("removeSession").Methods("POST")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-02 16:12:47.261744369 -0800 PST m=+0.007253685
|
||||
// 2017-12-02 19:31:37.021832102 -0800 PST m=+0.006773228
|
||||
|
||||
package static
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -222,6 +222,10 @@ input[type="text"]:focus {
|
|||
box-shadow: 0 0 8px rgba(82, 168, 236, 0.6);
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
::-moz-placeholder,
|
||||
::-ms-input-placeholder,
|
||||
::-webkit-input-placeholder {
|
||||
|
@ -327,7 +331,7 @@ a.button {
|
|||
/* Panel */
|
||||
.panel {
|
||||
color: #333;
|
||||
background-color: #f0f0f0;
|
||||
background-color: #fcfcfc;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
|
@ -497,6 +501,10 @@ a.button {
|
|||
color: #666;
|
||||
}
|
||||
|
||||
.entry-actions {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.entry-meta {
|
||||
font-size: 0.95em;
|
||||
margin: 0 0 20px;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-02 16:12:47.268772139 -0800 PST m=+0.014281455
|
||||
// 2017-12-02 19:31:37.026479459 -0800 PST m=+0.011420585
|
||||
|
||||
package static
|
||||
|
||||
|
@ -42,13 +42,16 @@ getCsrfToken(){let element=document.querySelector("meta[name=X-CSRF-Token]");if(
|
|||
return "";}
|
||||
execute(){fetch(new Request(this.url,this.options)).then((response)=>{if(this.callback){this.callback(response);}});}}
|
||||
class EntryHandler{static updateEntriesStatus(entryIDs,status){let url=document.body.dataset.entriesStatusUrl;let request=new RequestBuilder(url);request.withBody({entry_ids:entryIDs,status:status});request.execute();}
|
||||
static toggleEntryStatus(element){let entryID=parseInt(element.dataset.id,10);let statuses={read:"unread",unread:"read"};for(let currentStatus in statuses){let newStatus=statuses[currentStatus];if(element.classList.contains("item-status-"+currentStatus)){element.classList.remove("item-status-"+currentStatus);element.classList.add("item-status-"+newStatus);this.updateEntriesStatus([entryID],newStatus);break;}}}}
|
||||
static toggleEntryStatus(element){let entryID=parseInt(element.dataset.id,10);let statuses={read:"unread",unread:"read"};for(let currentStatus in statuses){let newStatus=statuses[currentStatus];if(element.classList.contains("item-status-"+currentStatus)){element.classList.remove("item-status-"+currentStatus);element.classList.add("item-status-"+newStatus);this.updateEntriesStatus([entryID],newStatus);break;}}}
|
||||
static saveEntry(element){if(element.dataset.completed){return;}
|
||||
element.innerHTML=element.dataset.labelLoading;let request=new RequestBuilder(element.dataset.saveUrl);request.withCallback(()=>{element.innerHTML=element.dataset.labelDone;element.dataset.completed=true;});request.execute();}}
|
||||
class ConfirmHandler{remove(url){let request=new RequestBuilder(url);request.withCallback(()=>window.location.reload());request.execute();}
|
||||
handle(event){let questionElement=document.createElement("span");let linkElement=event.target;let containerElement=linkElement.parentNode;linkElement.style.display="none";let yesElement=document.createElement("a");yesElement.href="#";yesElement.appendChild(document.createTextNode(linkElement.dataset.labelYes));yesElement.onclick=(event)=>{event.preventDefault();let loadingElement=document.createElement("span");loadingElement.className="loading";loadingElement.appendChild(document.createTextNode(linkElement.dataset.labelLoading));questionElement.remove();containerElement.appendChild(loadingElement);this.remove(linkElement.dataset.url);};let noElement=document.createElement("a");noElement.href="#";noElement.appendChild(document.createTextNode(linkElement.dataset.labelNo));noElement.onclick=(event)=>{event.preventDefault();linkElement.style.display="inline";questionElement.remove();};questionElement.className="confirm";questionElement.appendChild(document.createTextNode(linkElement.dataset.labelQuestion+" "));questionElement.appendChild(yesElement);questionElement.appendChild(document.createTextNode(", "));questionElement.appendChild(noElement);containerElement.appendChild(questionElement);}}
|
||||
class MenuHandler{clickMenuListItem(event){let element=event.target;if(element.tagName==="A"){window.location.href=element.getAttribute("href");}else{window.location.href=element.querySelector("a").getAttribute("href");}}
|
||||
toggleMainMenu(){let menu=document.querySelector(".header nav ul");if(DomHelper.isVisible(menu)){menu.style.display="none";}else{menu.style.display="block";}}}
|
||||
class NavHandler{markPageAsRead(){let items=DomHelper.getVisibleElements(".items .item");let entryIDs=[];items.forEach((element)=>{element.classList.add("item-status-read");entryIDs.push(parseInt(element.dataset.id,10));});if(entryIDs.length>0){EntryHandler.updateEntriesStatus(entryIDs,"read");}
|
||||
this.goToPage("next");}
|
||||
saveEntry(){if(this.isListView()){let currentItem=document.querySelector(".current-item");if(currentItem!==null){let saveLink=currentItem.querySelector("a[data-save-entry]");if(saveLink){EntryHandler.saveEntry(saveLink);}}}else{let saveLink=document.querySelector("a[data-save-entry]");if(saveLink){EntryHandler.saveEntry(saveLink);}}}
|
||||
toggleEntryStatus(){let currentItem=document.querySelector(".current-item");if(currentItem!==null){EntryHandler.toggleEntryStatus(currentItem);this.goToNextListItem();}}
|
||||
openOriginalLink(){let entryLink=document.querySelector(".entry h1 a");if(entryLink!==null){DomHelper.openNewTab(entryLink.getAttribute("href"));return;}
|
||||
let currentItemOriginalLink=document.querySelector(".current-item a[data-original-link]");if(currentItemOriginalLink!==null){DomHelper.openNewTab(currentItemOriginalLink.getAttribute("href"));}}
|
||||
|
@ -65,9 +68,9 @@ if(document.querySelector(".current-item")===null){items[0].classList.add("curre
|
|||
for(let i=0;i<items.length;i++){if(items[i].classList.contains("current-item")){items[i].classList.remove("current-item");if(i+1<items.length){items[i+1].classList.add("current-item");DomHelper.scrollPageTo(items[i+1]);}
|
||||
break;}}}
|
||||
isListView(){return document.querySelector(".items")!==null;}}
|
||||
document.addEventListener("DOMContentLoaded",function(){FormHandler.handleSubmitButtons();let touchHandler=new TouchHandler();touchHandler.listen();let navHandler=new NavHandler();let keyboardHandler=new KeyboardHandler();keyboardHandler.on("g u",()=>navHandler.goToPage("unread"));keyboardHandler.on("g h",()=>navHandler.goToPage("history"));keyboardHandler.on("g f",()=>navHandler.goToPage("feeds"));keyboardHandler.on("g c",()=>navHandler.goToPage("categories"));keyboardHandler.on("g s",()=>navHandler.goToPage("settings"));keyboardHandler.on("ArrowLeft",()=>navHandler.goToPrevious());keyboardHandler.on("ArrowRight",()=>navHandler.goToNext());keyboardHandler.on("j",()=>navHandler.goToPrevious());keyboardHandler.on("p",()=>navHandler.goToPrevious());keyboardHandler.on("k",()=>navHandler.goToNext());keyboardHandler.on("n",()=>navHandler.goToNext());keyboardHandler.on("h",()=>navHandler.goToPage("previous"));keyboardHandler.on("l",()=>navHandler.goToPage("next"));keyboardHandler.on("o",()=>navHandler.openSelectedItem());keyboardHandler.on("v",()=>navHandler.openOriginalLink());keyboardHandler.on("m",()=>navHandler.toggleEntryStatus());keyboardHandler.on("A",()=>navHandler.markPageAsRead());keyboardHandler.listen();let mouseHandler=new MouseHandler();mouseHandler.onClick("a[data-on-click=markPageAsRead]",()=>navHandler.markPageAsRead());mouseHandler.onClick("a[data-confirm]",(event)=>{(new ConfirmHandler()).handle(event);});if(document.documentElement.clientWidth<600){let menuHandler=new MenuHandler();mouseHandler.onClick(".logo",()=>menuHandler.toggleMainMenu());mouseHandler.onClick(".header nav li",(event)=>menuHandler.clickMenuListItem(event));}});})();`,
|
||||
document.addEventListener("DOMContentLoaded",function(){FormHandler.handleSubmitButtons();let touchHandler=new TouchHandler();touchHandler.listen();let navHandler=new NavHandler();let keyboardHandler=new KeyboardHandler();keyboardHandler.on("g u",()=>navHandler.goToPage("unread"));keyboardHandler.on("g h",()=>navHandler.goToPage("history"));keyboardHandler.on("g f",()=>navHandler.goToPage("feeds"));keyboardHandler.on("g c",()=>navHandler.goToPage("categories"));keyboardHandler.on("g s",()=>navHandler.goToPage("settings"));keyboardHandler.on("ArrowLeft",()=>navHandler.goToPrevious());keyboardHandler.on("ArrowRight",()=>navHandler.goToNext());keyboardHandler.on("j",()=>navHandler.goToPrevious());keyboardHandler.on("p",()=>navHandler.goToPrevious());keyboardHandler.on("k",()=>navHandler.goToNext());keyboardHandler.on("n",()=>navHandler.goToNext());keyboardHandler.on("h",()=>navHandler.goToPage("previous"));keyboardHandler.on("l",()=>navHandler.goToPage("next"));keyboardHandler.on("o",()=>navHandler.openSelectedItem());keyboardHandler.on("v",()=>navHandler.openOriginalLink());keyboardHandler.on("m",()=>navHandler.toggleEntryStatus());keyboardHandler.on("A",()=>navHandler.markPageAsRead());keyboardHandler.on("s",()=>navHandler.saveEntry());keyboardHandler.listen();let mouseHandler=new MouseHandler();mouseHandler.onClick("a[data-save-entry]",(event)=>{event.preventDefault();EntryHandler.saveEntry(event.target);});mouseHandler.onClick("a[data-on-click=markPageAsRead]",()=>navHandler.markPageAsRead());mouseHandler.onClick("a[data-confirm]",(event)=>{(new ConfirmHandler()).handle(event);});if(document.documentElement.clientWidth<600){let menuHandler=new MenuHandler();mouseHandler.onClick(".logo",()=>menuHandler.toggleMainMenu());mouseHandler.onClick(".header nav li",(event)=>menuHandler.clickMenuListItem(event));}});})();`,
|
||||
}
|
||||
|
||||
var JavascriptChecksums = map[string]string{
|
||||
"app": "e1a955eaf7c0672da771ddbb87090bd7831197c46c5508349aa87a955f7814f6",
|
||||
"app": "ce9791d6752f8b09f6163907e5ba01092f76595d6f99d611858216d6533f4655",
|
||||
}
|
||||
|
|
|
@ -298,6 +298,21 @@ class EntryHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
static saveEntry(element) {
|
||||
if (element.dataset.completed) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.innerHTML = element.dataset.labelLoading;
|
||||
|
||||
let request = new RequestBuilder(element.dataset.saveUrl);
|
||||
request.withCallback(() => {
|
||||
element.innerHTML = element.dataset.labelDone;
|
||||
element.dataset.completed = true;
|
||||
});
|
||||
request.execute();
|
||||
}
|
||||
}
|
||||
|
||||
class ConfirmHandler {
|
||||
|
@ -386,6 +401,23 @@ class NavHandler {
|
|||
this.goToPage("next");
|
||||
}
|
||||
|
||||
saveEntry() {
|
||||
if (this.isListView()) {
|
||||
let currentItem = document.querySelector(".current-item");
|
||||
if (currentItem !== null) {
|
||||
let saveLink = currentItem.querySelector("a[data-save-entry]");
|
||||
if (saveLink) {
|
||||
EntryHandler.saveEntry(saveLink);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let saveLink = document.querySelector("a[data-save-entry]");
|
||||
if (saveLink) {
|
||||
EntryHandler.saveEntry(saveLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleEntryStatus() {
|
||||
let currentItem = document.querySelector(".current-item");
|
||||
if (currentItem !== null) {
|
||||
|
@ -520,9 +552,14 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
keyboardHandler.on("v", () => navHandler.openOriginalLink());
|
||||
keyboardHandler.on("m", () => navHandler.toggleEntryStatus());
|
||||
keyboardHandler.on("A", () => navHandler.markPageAsRead());
|
||||
keyboardHandler.on("s", () => navHandler.saveEntry());
|
||||
keyboardHandler.listen();
|
||||
|
||||
let mouseHandler = new MouseHandler();
|
||||
mouseHandler.onClick("a[data-save-entry]", (event) => {
|
||||
event.preventDefault();
|
||||
EntryHandler.saveEntry(event.target);
|
||||
});
|
||||
mouseHandler.onClick("a[data-on-click=markPageAsRead]", () => navHandler.markPageAsRead());
|
||||
mouseHandler.onClick("a[data-confirm]", (event) => {
|
||||
(new ConfirmHandler()).handle(event);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-02 16:12:47.286110197 -0800 PST m=+0.031619513
|
||||
// 2017-12-02 19:31:37.041375649 -0800 PST m=+0.026316775
|
||||
|
||||
package template
|
||||
|
||||
|
|
|
@ -35,6 +35,15 @@
|
|||
<li>
|
||||
<time datetime="{{ isodate .Date }}" title="{{ isodate .Date }}">{{ elapsed .Date }}</time>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<label for="form-confirmation">{{ t "Confirmation" }}</label>
|
||||
<input type="password" name="confirmation" id="form-confirmation" value="{{ .form.Confirmation }}" required>
|
||||
|
||||
<label><input type="checkbox" name="is_admin" value="1" {{ if .form.IsAdmin }}checked="checked"{{ end }}> {{ t "Administrator" }}</label>
|
||||
<label><input type="checkbox" name="is_admin" value="1" {{ if .form.IsAdmin }}checked{{ end }}> {{ t "Administrator" }}</label>
|
||||
|
||||
<div class="buttons">
|
||||
<button type="submit" class="button button-primary" data-label-loading="{{ t "Loading..." }}">{{ t "Save" }}</button> {{ t "or" }} <a href="{{ route "users" }}">{{ t "cancel" }}</a>
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<label for="form-confirmation">{{ t "Confirmation" }}</label>
|
||||
<input type="password" name="confirmation" id="form-confirmation" value="{{ .form.Confirmation }}">
|
||||
|
||||
<label><input type="checkbox" name="is_admin" value="1" {{ if .form.IsAdmin }}checked="checked"{{ end }}> {{ t "Administrator" }}</label>
|
||||
<label><input type="checkbox" name="is_admin" value="1" {{ if .form.IsAdmin }}checked{{ end }}> {{ t "Administrator" }}</label>
|
||||
|
||||
<div class="buttons">
|
||||
<button type="submit" class="button button-primary" data-label-loading="{{ t "Loading..." }}">{{ t "Update" }}</button> {{ t "or" }} <a href="{{ route "users" }}">{{ t "cancel" }}</a>
|
||||
|
|
|
@ -6,6 +6,15 @@
|
|||
<h1>
|
||||
<a href="{{ .entry.URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer">{{ .entry.Title }}</a>
|
||||
</h1>
|
||||
<div class="entry-actions">
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .entry.ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</div>
|
||||
<div class="entry-meta">
|
||||
<span class="entry-website">
|
||||
{{ if ne .entry.Feed.Icon.IconID 0 }}
|
||||
|
|
|
@ -46,6 +46,15 @@
|
|||
<li>
|
||||
<time datetime="{{ isodate .Date }}" title="{{ isodate .Date }}">{{ elapsed .Date }}</time>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
|
|
|
@ -35,6 +35,15 @@
|
|||
<li>
|
||||
<time datetime="{{ isodate .Date }}" title="{{ isodate .Date }}">{{ elapsed .Date }}</time>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
|
|
|
@ -21,6 +21,33 @@
|
|||
</ul>
|
||||
</section>
|
||||
|
||||
<form method="post" autocomplete="off" action="{{ route "updateIntegration" }}">
|
||||
<input type="hidden" name="csrf" value="{{ .csrf }}">
|
||||
|
||||
{{ if .errorMessage }}
|
||||
<div class="alert alert-error">{{ t .errorMessage }}</div>
|
||||
{{ end }}
|
||||
|
||||
<h3>Pinboard</h3>
|
||||
<label>
|
||||
<input type="checkbox" name="pinboard_enabled" value="1" {{ if .form.PinboardEnabled }}checked{{ end }}> {{ t "Enable Pinboard" }}
|
||||
</label>
|
||||
|
||||
<label for="form-pinboard-token">{{ t "Pinboard API Token" }}</label>
|
||||
<input type="password" name="pinboard_token" id="form-pinboard-token" value="{{ .form.PinboardToken }}">
|
||||
|
||||
<label for="form-pinboard-tags">{{ t "Pinboard Tags" }}</label>
|
||||
<input type="text" name="pinboard_tags" id="form-pinboard-tags" value="{{ .form.PinboardTags }}">
|
||||
|
||||
<label>
|
||||
<input type="checkbox" name="pinboard_mark_as_unread" value="1" {{ if .form.PinboardMarkAsUnread }}checked{{ end }}> {{ t "Mark bookmark as unread" }}
|
||||
</label>
|
||||
|
||||
<div class="buttons">
|
||||
<button type="submit" class="button button-primary" data-label-loading="{{ t "Loading..." }}">{{ t "Update" }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="panel">
|
||||
<h3>{{ t "Bookmarklet" }}</h3>
|
||||
<p>{{ t "This special link allows you to subscribe to a website directly by using a bookmark in your web browser." }}</p>
|
||||
|
|
|
@ -35,6 +35,15 @@
|
|||
<li>
|
||||
<time datetime="{{ isodate .Date }}" title="{{ isodate .Date }}">{{ elapsed .Date }}</time>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-02 16:12:47.271430439 -0800 PST m=+0.016939755
|
||||
// 2017-12-02 19:31:37.027317932 -0800 PST m=+0.012259058
|
||||
|
||||
package template
|
||||
|
||||
|
@ -185,6 +185,15 @@ var templateViewsMap = map[string]string{
|
|||
<li>
|
||||
<time datetime="{{ isodate .Date }}" title="{{ isodate .Date }}">{{ elapsed .Date }}</time>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
|
@ -300,7 +309,7 @@ var templateViewsMap = map[string]string{
|
|||
<label for="form-confirmation">{{ t "Confirmation" }}</label>
|
||||
<input type="password" name="confirmation" id="form-confirmation" value="{{ .form.Confirmation }}" required>
|
||||
|
||||
<label><input type="checkbox" name="is_admin" value="1" {{ if .form.IsAdmin }}checked="checked"{{ end }}> {{ t "Administrator" }}</label>
|
||||
<label><input type="checkbox" name="is_admin" value="1" {{ if .form.IsAdmin }}checked{{ end }}> {{ t "Administrator" }}</label>
|
||||
|
||||
<div class="buttons">
|
||||
<button type="submit" class="button button-primary" data-label-loading="{{ t "Loading..." }}">{{ t "Save" }}</button> {{ t "or" }} <a href="{{ route "users" }}">{{ t "cancel" }}</a>
|
||||
|
@ -440,7 +449,7 @@ var templateViewsMap = map[string]string{
|
|||
<label for="form-confirmation">{{ t "Confirmation" }}</label>
|
||||
<input type="password" name="confirmation" id="form-confirmation" value="{{ .form.Confirmation }}">
|
||||
|
||||
<label><input type="checkbox" name="is_admin" value="1" {{ if .form.IsAdmin }}checked="checked"{{ end }}> {{ t "Administrator" }}</label>
|
||||
<label><input type="checkbox" name="is_admin" value="1" {{ if .form.IsAdmin }}checked{{ end }}> {{ t "Administrator" }}</label>
|
||||
|
||||
<div class="buttons">
|
||||
<button type="submit" class="button button-primary" data-label-loading="{{ t "Loading..." }}">{{ t "Update" }}</button> {{ t "or" }} <a href="{{ route "users" }}">{{ t "cancel" }}</a>
|
||||
|
@ -456,6 +465,15 @@ var templateViewsMap = map[string]string{
|
|||
<h1>
|
||||
<a href="{{ .entry.URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer">{{ .entry.Title }}</a>
|
||||
</h1>
|
||||
<div class="entry-actions">
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .entry.ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</div>
|
||||
<div class="entry-meta">
|
||||
<span class="entry-website">
|
||||
{{ if ne .entry.Feed.Icon.IconID 0 }}
|
||||
|
@ -572,6 +590,15 @@ var templateViewsMap = map[string]string{
|
|||
<li>
|
||||
<time datetime="{{ isodate .Date }}" title="{{ isodate .Date }}">{{ elapsed .Date }}</time>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
|
@ -697,6 +724,15 @@ var templateViewsMap = map[string]string{
|
|||
<li>
|
||||
<time datetime="{{ isodate .Date }}" title="{{ isodate .Date }}">{{ elapsed .Date }}</time>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
|
@ -768,6 +804,33 @@ var templateViewsMap = map[string]string{
|
|||
</ul>
|
||||
</section>
|
||||
|
||||
<form method="post" autocomplete="off" action="{{ route "updateIntegration" }}">
|
||||
<input type="hidden" name="csrf" value="{{ .csrf }}">
|
||||
|
||||
{{ if .errorMessage }}
|
||||
<div class="alert alert-error">{{ t .errorMessage }}</div>
|
||||
{{ end }}
|
||||
|
||||
<h3>Pinboard</h3>
|
||||
<label>
|
||||
<input type="checkbox" name="pinboard_enabled" value="1" {{ if .form.PinboardEnabled }}checked{{ end }}> {{ t "Enable Pinboard" }}
|
||||
</label>
|
||||
|
||||
<label for="form-pinboard-token">{{ t "Pinboard API Token" }}</label>
|
||||
<input type="password" name="pinboard_token" id="form-pinboard-token" value="{{ .form.PinboardToken }}">
|
||||
|
||||
<label for="form-pinboard-tags">{{ t "Pinboard Tags" }}</label>
|
||||
<input type="text" name="pinboard_tags" id="form-pinboard-tags" value="{{ .form.PinboardTags }}">
|
||||
|
||||
<label>
|
||||
<input type="checkbox" name="pinboard_mark_as_unread" value="1" {{ if .form.PinboardMarkAsUnread }}checked{{ end }}> {{ t "Mark bookmark as unread" }}
|
||||
</label>
|
||||
|
||||
<div class="buttons">
|
||||
<button type="submit" class="button button-primary" data-label-loading="{{ t "Loading..." }}">{{ t "Update" }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="panel">
|
||||
<h3>{{ t "Bookmarklet" }}</h3>
|
||||
<p>{{ t "This special link allows you to subscribe to a website directly by using a bookmark in your web browser." }}</p>
|
||||
|
@ -982,6 +1045,15 @@ var templateViewsMap = map[string]string{
|
|||
<li>
|
||||
<time datetime="{{ isodate .Date }}" title="{{ isodate .Date }}">{{ elapsed .Date }}</time>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
title="{{ t "Save this article" }}"
|
||||
data-save-entry="true"
|
||||
data-save-url="{{ route "saveEntry" "entryID" .ID }}"
|
||||
data-label-loading="{{ t "Saving..." }}"
|
||||
data-label-done="{{ t "Done!" }}"
|
||||
>{{ t "Save" }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ t "Original" }}</a>
|
||||
</li>
|
||||
|
@ -1061,22 +1133,22 @@ var templateViewsMapChecksums = map[string]string{
|
|||
"about": "ad2fb778fc73c39b733b3f81b13e5c7d689b041fadd24ee2d4577f545aa788ad",
|
||||
"add_subscription": "098ea9e492e18242bd414b22c4d8638006d113f728e5ae78c9186663f60ae3f1",
|
||||
"categories": "ca1280cd157bb527d4fc907da67b05a8347378f6dce965b9389d4bcdf3600a11",
|
||||
"category_entries": "23187062ffa6ba8a37ab8a3db02b08ca3d6013c1b57a2aa96b8f9f0b1f237389",
|
||||
"category_entries": "951cdacf38fcaed5cdd63a00dc800e26039236b94b556a68e4409012b0095ece",
|
||||
"choose_subscription": "d37682743d8bbd84738a964e238103db2651f95fa340c6e285ffe2e12548d673",
|
||||
"create_category": "2b82af5d2dcd67898dc5daa57a6461e6ff8121a6089b2a2a1be909f35e4a2275",
|
||||
"create_user": "815dd31faaa6e9ba81a2a6664e5707aaf4153c392accd2b1f77cf1937035a881",
|
||||
"create_user": "45e226df757126d5fe7c464e295e9a34f07952cfdb71e31e49839850d35af139",
|
||||
"edit_category": "cee720faadcec58289b707ad30af623d2ee66c1ce23a732965463250d7ff41c5",
|
||||
"edit_feed": "c5bc4c22bf7e8348d880395250545595d21fb8c8e723fc5d7cca68e25d250884",
|
||||
"edit_user": "c835d78f7cf36c11533db9cef253457a9003987d704070d59446cb2b0e84dcb9",
|
||||
"entry": "12e863c777368185091008adc851ddc0327317849f1566f7803be2b5bd358fd7",
|
||||
"feed_entries": "0d9818a09b92ee9a80287e34cd3a08598f71a7c03a2658893db1b8b731dafbc9",
|
||||
"edit_user": "82d9749d76ddbd2352816d813c4b1f6d92f2222de678b4afe5821090246735c7",
|
||||
"entry": "fc800833ef8a9875f8ec44e40d5e53aa7dae91b5f78f7f05dac3f627129e1984",
|
||||
"feed_entries": "547c19eb36b20e350ce70ed045173b064cdcd6b114afb241c9f2dda9d88fcc27",
|
||||
"feeds": "c22af39b42ba9ca69ea0914ca789303ec2c5b484abcd4eaa49016e365381257c",
|
||||
"history": "bfad256437cfcc898dea9e7e9a2c331ea707f1729cdb559ea563a2cb8de6cc7d",
|
||||
"history": "9a67599a5d8d67ef958e3f07da339b749f42892667547c9e60a54477e8d32a56",
|
||||
"import": "73b5112e20bfd232bf73334544186ea419505936bc237d481517a8622901878f",
|
||||
"integrations": "c485d6d9ed996635e55e73320610e6bcb01a41c1153e8e739ae2294b0b14b243",
|
||||
"integrations": "e3cb653bf3d45fada18b64c53860fcae18a9a3f18162d42c56b290cd1aaa4e18",
|
||||
"login": "04f3ce79bfa5753f69e0d956c2a8999c0da549c7925634a3e8134975da0b0e0f",
|
||||
"sessions": "878dbe8f8ea783b44130c495814179519fa5c3aa2666ac87508f94d58dd008bf",
|
||||
"settings": "ea2505b9d0a6d6bb594dba87a92079de19baa6d494f0651693a7685489fb7de9",
|
||||
"unread": "3d8deab9119dc11f0d74a461e1ac89dc29931ba4645a043bb5b3eccba3cba5b8",
|
||||
"unread": "745d9a1c70c7327aa0ae37328c2736ba6a5f6493db44ef7f12d4da241491b71f",
|
||||
"users": "44677e28bb5347799ed0020c90ec785aadec4b1454446d92411cfdaf6e32110b",
|
||||
}
|
||||
|
|
|
@ -4,10 +4,25 @@
|
|||
|
||||
package controller
|
||||
|
||||
import "github.com/miniflux/miniflux2/server/core"
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/miniflux/miniflux2/integration/pinboard"
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"github.com/miniflux/miniflux2/server/ui/form"
|
||||
)
|
||||
|
||||
// ShowIntegrations renders the page with all external integrations.
|
||||
func (c *Controller) ShowIntegrations(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.LoggedUser()
|
||||
integration, err := c.store.Integration(user.ID)
|
||||
if err != nil {
|
||||
response.HTML().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.HTML().ServerError(err)
|
||||
|
@ -16,5 +31,75 @@ func (c *Controller) ShowIntegrations(ctx *core.Context, request *core.Request,
|
|||
|
||||
response.HTML().Render("integrations", args.Merge(tplParams{
|
||||
"menu": "settings",
|
||||
"form": form.IntegrationForm{
|
||||
PinboardEnabled: integration.PinboardEnabled,
|
||||
PinboardToken: integration.PinboardToken,
|
||||
PinboardTags: integration.PinboardTags,
|
||||
PinboardMarkAsUnread: integration.PinboardMarkAsUnread,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
// UpdateIntegration updates integration settings.
|
||||
func (c *Controller) UpdateIntegration(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.LoggedUser()
|
||||
integration, err := c.store.Integration(user.ID)
|
||||
if err != nil {
|
||||
response.HTML().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
integrationForm := form.NewIntegrationForm(request.Request())
|
||||
integrationForm.Merge(integration)
|
||||
|
||||
err = c.store.UpdateIntegration(integration)
|
||||
if err != nil {
|
||||
response.HTML().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.Route("integrations"))
|
||||
}
|
||||
|
||||
// SaveEntry send the link to external services.
|
||||
func (c *Controller) SaveEntry(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
entryID, err := request.IntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.HTML().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
user := ctx.LoggedUser()
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithEntryID(entryID)
|
||||
builder.WithoutStatus(model.EntryStatusRemoved)
|
||||
|
||||
entry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.JSON().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
response.JSON().NotFound(errors.New("Entry not found"))
|
||||
return
|
||||
}
|
||||
|
||||
integration, err := c.store.Integration(user.ID)
|
||||
if err != nil {
|
||||
response.JSON().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if integration.PinboardEnabled {
|
||||
client := pinboard.NewClient(integration.PinboardToken)
|
||||
err := client.AddBookmark(entry.URL, entry.Title, integration.PinboardTags, integration.PinboardMarkAsUnread)
|
||||
if err != nil {
|
||||
log.Println("[Pinboard]", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
response.JSON().Created(map[string]string{"message": "saved"})
|
||||
}
|
||||
|
|
37
server/ui/form/integration.go
Normal file
37
server/ui/form/integration.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package form
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
)
|
||||
|
||||
// IntegrationForm represents user integration settings form.
|
||||
type IntegrationForm struct {
|
||||
PinboardEnabled bool
|
||||
PinboardToken string
|
||||
PinboardTags string
|
||||
PinboardMarkAsUnread bool
|
||||
}
|
||||
|
||||
// Merge copy form values to the model.
|
||||
func (i IntegrationForm) Merge(integration *model.Integration) {
|
||||
integration.PinboardEnabled = i.PinboardEnabled
|
||||
integration.PinboardToken = i.PinboardToken
|
||||
integration.PinboardTags = i.PinboardTags
|
||||
integration.PinboardMarkAsUnread = i.PinboardMarkAsUnread
|
||||
}
|
||||
|
||||
// NewIntegrationForm returns a new AuthForm.
|
||||
func NewIntegrationForm(r *http.Request) *IntegrationForm {
|
||||
return &IntegrationForm{
|
||||
PinboardEnabled: r.FormValue("pinboard_enabled") == "1",
|
||||
PinboardToken: r.FormValue("pinboard_token"),
|
||||
PinboardTags: r.FormValue("pinboard_tags"),
|
||||
PinboardMarkAsUnread: r.FormValue("pinboard_mark_as_unread") == "1",
|
||||
}
|
||||
}
|
8
sql/schema_version_5.sql
Normal file
8
sql/schema_version_5.sql
Normal file
|
@ -0,0 +1,8 @@
|
|||
create table integrations (
|
||||
user_id int not null,
|
||||
pinboard_enabled bool default 'f',
|
||||
pinboard_token text default '',
|
||||
pinboard_tags text default 'miniflux',
|
||||
pinboard_mark_as_unread bool default 'f',
|
||||
primary key(user_id)
|
||||
)
|
12
sql/sql.go
12
sql/sql.go
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-02 16:12:47.256865279 -0800 PST m=+0.002374595
|
||||
// 2017-12-02 19:31:37.017577269 -0800 PST m=+0.002518395
|
||||
|
||||
package sql
|
||||
|
||||
|
@ -120,6 +120,15 @@ create index users_extra_idx on users using gin(extra);
|
|||
);`,
|
||||
"schema_version_4": `create type entry_sorting_direction as enum('asc', 'desc');
|
||||
alter table users add column entry_direction entry_sorting_direction default 'asc';
|
||||
`,
|
||||
"schema_version_5": `create table integrations (
|
||||
user_id int not null,
|
||||
pinboard_enabled bool default 'f',
|
||||
pinboard_token text default '',
|
||||
pinboard_tags text default 'miniflux',
|
||||
pinboard_mark_as_unread bool default 'f',
|
||||
primary key(user_id)
|
||||
)
|
||||
`,
|
||||
}
|
||||
|
||||
|
@ -128,4 +137,5 @@ var SqlMapChecksums = map[string]string{
|
|||
"schema_version_2": "e8e9ff32478df04fcddad10a34cba2e8bb1e67e7977b5bd6cdc4c31ec94282b4",
|
||||
"schema_version_3": "a54745dbc1c51c000f74d4e5068f1e2f43e83309f023415b1749a47d5c1e0f12",
|
||||
"schema_version_4": "216ea3a7d3e1704e40c797b5dc47456517c27dbb6ca98bf88812f4f63d74b5d9",
|
||||
"schema_version_5": "69c0aff4d72f86a3f3c3cac33674a943d4f293dd88a8552706144814a85b5629",
|
||||
}
|
||||
|
|
78
storage/integration.go
Normal file
78
storage/integration.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
)
|
||||
|
||||
// Integration returns user integration settings.
|
||||
func (s *Storage) Integration(userID int64) (*model.Integration, error) {
|
||||
query := `SELECT
|
||||
user_id,
|
||||
pinboard_enabled,
|
||||
pinboard_token,
|
||||
pinboard_tags,
|
||||
pinboard_mark_as_unread
|
||||
FROM integrations
|
||||
WHERE user_id=$1
|
||||
`
|
||||
var integration model.Integration
|
||||
err := s.db.QueryRow(query, userID).Scan(
|
||||
&integration.UserID,
|
||||
&integration.PinboardEnabled,
|
||||
&integration.PinboardToken,
|
||||
&integration.PinboardTags,
|
||||
&integration.PinboardMarkAsUnread,
|
||||
)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return nil, nil
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("unable to fetch integration row: %v", err)
|
||||
}
|
||||
|
||||
return &integration, nil
|
||||
}
|
||||
|
||||
// UpdateIntegration saves user integration settings.
|
||||
func (s *Storage) UpdateIntegration(integration *model.Integration) error {
|
||||
query := `
|
||||
UPDATE integrations SET
|
||||
pinboard_enabled=$1,
|
||||
pinboard_token=$2,
|
||||
pinboard_tags=$3,
|
||||
pinboard_mark_as_unread=$4
|
||||
WHERE user_id=$5
|
||||
`
|
||||
_, err := s.db.Exec(
|
||||
query,
|
||||
integration.PinboardEnabled,
|
||||
integration.PinboardToken,
|
||||
integration.PinboardTags,
|
||||
integration.PinboardMarkAsUnread,
|
||||
integration.UserID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update integration row: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateIntegration creates initial user integration settings.
|
||||
func (s *Storage) CreateIntegration(userID int64) error {
|
||||
query := `INSERT INTO integrations (user_id) VALUES ($1)`
|
||||
_, err := s.db.Exec(query, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create integration row: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/miniflux/miniflux2/sql"
|
||||
)
|
||||
|
||||
const schemaVersion = 4
|
||||
const schemaVersion = 5
|
||||
|
||||
// Migrate run database migrations.
|
||||
func (s *Storage) Migrate() {
|
||||
|
|
|
@ -86,6 +86,7 @@ func (s *Storage) CreateUser(user *model.User) (err error) {
|
|||
}
|
||||
|
||||
s.CreateCategory(&model.Category{Title: "All", UserID: user.ID})
|
||||
s.CreateIntegration(user.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue