浏览代码

feat: support force refresh in feed edit and feed entries page

njzy 2 年之前
父节点
当前提交
79c91d71c8

+ 1 - 1
api/feed.go

@@ -47,7 +47,7 @@ func (h *handler) refreshFeed(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	err := feedHandler.RefreshFeed(h.store, userID, feedID)
+	err := feedHandler.RefreshFeed(h.store, userID, feedID, false)
 	if err != nil {
 		json.ServerError(w, r, err)
 		return

+ 1 - 1
cli/refresh_feeds.go

@@ -34,7 +34,7 @@ func refreshFeeds(store *storage.Storage) {
 			defer wg.Done()
 			for job := range jobQueue {
 				logger.Info("[Cronjob] Refreshing feed #%d for user #%d in worker #%d", job.FeedID, job.UserID, workerID)
-				if err := feedHandler.RefreshFeed(store, job.UserID, job.FeedID); err != nil {
+				if err := feedHandler.RefreshFeed(store, job.UserID, job.FeedID, false); err != nil {
 					logger.Error("[Cronjob] Refreshing the feed #%d returned this error: %v", job.FeedID, err)
 				}
 			}

+ 16 - 0
http/request/params.go

@@ -107,6 +107,22 @@ func QueryInt64Param(r *http.Request, param string, defaultValue int64) int64 {
 	return val
 }
 
+// QueryBoolParam returns a query string parameter as bool.
+func QueryBoolParam(r *http.Request, param string, defaultValue bool) bool {
+	value := r.URL.Query().Get(param)
+	if value == "" {
+		return defaultValue
+	}
+
+	val, err := strconv.ParseBool(value)
+
+	if err != nil {
+		return defaultValue
+	}
+
+	return val
+}
+
 // HasQueryParam checks if the query string contains the given parameter.
 func HasQueryParam(r *http.Request, param string) bool {
 	values := r.URL.Query()

+ 1 - 0
locale/translations/de_DE.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Sind Sie sicher?",
+    "confirm.question.refresh": "Möchten Sie eine erzwungene Aktualisierung durchführen?",
     "confirm.yes": "ja",
     "confirm.no": "nein",
     "confirm.loading": "In Arbeit...",

+ 1 - 0
locale/translations/el_EL.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Είστε σίγουροι;",
+    "confirm.question.refresh": "Θέλετε να επιτελέσετε μια υποχρεωτική ανανέωση;",
     "confirm.yes": "ναι",
     "confirm.no": "όχι",
     "confirm.loading": "Σε εξέλιξη...",

+ 1 - 0
locale/translations/en_US.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Are you sure?",
+    "confirm.question.refresh": "Are you want to force refresh?",
     "confirm.yes": "yes",
     "confirm.no": "no",
     "confirm.loading": "In progress…",

+ 1 - 0
locale/translations/es_ES.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "¿Estás seguro?",
+    "confirm.question.refresh": "¿Quieres forzar la actualización?",
     "confirm.yes": "sí",
     "confirm.no": "no",
     "confirm.loading": "En progreso...",

+ 1 - 0
locale/translations/fi_FI.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Oletko varma?",
+    "confirm.question.refresh": "Haluatko pakottaa päivityksen?",
     "confirm.yes": "kyllä",
     "confirm.no": "ei",
     "confirm.loading": "Käynnissä...",

+ 1 - 0
locale/translations/fr_FR.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Êtes-vous sûr ?",
+    "confirm.question.refresh": "Voulez-vous forcer le rafraîchissement ?",
     "confirm.yes": "oui",
     "confirm.no": "non",
     "confirm.loading": "En cours...",

+ 1 - 0
locale/translations/hi_IN.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "मंजूर है?",
+    "confirm.question.refresh": "क्या आप बल द्वारा ताज़ा करना चाहते हैं?",
     "confirm.yes": "हाँ",
     "confirm.no": " नहीं",
     "confirm.loading": " प्रगति में है ...",

+ 1 - 0
locale/translations/id_ID.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Apakah Anda yakin?",
+    "confirm.question.refresh": "Apakah Anda ingin memaksa penyegaran?",
     "confirm.yes": "ya",
     "confirm.no": "tidak",
     "confirm.loading": "Sedang progres...",

+ 1 - 0
locale/translations/it_IT.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Sei sicuro?",
+    "confirm.question.refresh": "Vuoi forzare l'aggiornamento?",
     "confirm.yes": "sì",
     "confirm.no": "no",
     "confirm.loading": "In corso...",

+ 1 - 0
locale/translations/ja_JP.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "よろしいですか?",
+    "confirm.question.refresh": "強制的に更新しますか?",
     "confirm.yes": "はい",
     "confirm.no": "いいえ",
     "confirm.loading": "実行中…",

+ 1 - 0
locale/translations/nl_NL.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Weet je het zeker?",
+    "confirm.question.refresh": "Wil je een gedwongen vernieuwing uitvoeren?",
     "confirm.yes": "ja",
     "confirm.no": "nee",
     "confirm.loading": "Bezig...",

+ 1 - 0
locale/translations/pl_PL.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Czy jesteś pewny?",
+    "confirm.question.refresh": "Czy chcesz wymusić odświeżenie?",
     "confirm.yes": "tak",
     "confirm.no": "nie",
     "confirm.loading": "W toku...",

+ 1 - 0
locale/translations/pt_BR.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Tem certeza?",
+    "confirm.question.refresh": "Você deseja forçar a atualização?",
     "confirm.yes": "Sim",
     "confirm.no": "Não",
     "confirm.loading": "Carregando...",

+ 1 - 0
locale/translations/ru_RU.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Вы уверены?",
+    "confirm.question.refresh": "Вы хотите выполнить принудительное обновление?",
     "confirm.yes": "да",
     "confirm.no": "нет",
     "confirm.loading": "В процессе…",

+ 1 - 0
locale/translations/tr_TR.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "Emin misiniz?",
+    "confirm.question.refresh": "Zorla yenilemek istiyor musunuz?",
     "confirm.yes": "evet",
     "confirm.no": "hayır",
     "confirm.loading": "Devam ediyor...",

+ 1 - 0
locale/translations/uk_UA.json

@@ -1,5 +1,6 @@
 {
   "confirm.question": "Ви впевнені?",
+  "confirm.question.refresh": "Ви хочете змусити оновити?",
   "confirm.yes": "так",
   "confirm.no": "ні",
   "confirm.loading": "В процесі...",

+ 1 - 0
locale/translations/zh_CN.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "您确认吗?",
+    "confirm.question.refresh": "您是否要强制刷新?",
     "confirm.yes": "是",
     "confirm.no": "否",
     "confirm.loading": "执行中…",

+ 1 - 0
locale/translations/zh_TW.json

@@ -1,5 +1,6 @@
 {
     "confirm.question": "您確認嗎?",
+    "confirm.question.refresh": "您想要強制刷新嗎?",
     "confirm.yes": "是",
     "confirm.no": "否",
     "confirm.loading": "執行中…",

+ 6 - 5
reader/handler/handler.go

@@ -83,7 +83,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
 	subscription.WithClientResponse(response)
 	subscription.CheckedNow()
 
-	processor.ProcessFeedEntries(store, subscription, user)
+	processor.ProcessFeedEntries(store, subscription, user, true)
 
 	if storeErr := store.CreateFeed(subscription); storeErr != nil {
 		return nil, storeErr
@@ -104,7 +104,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
 }
 
 // RefreshFeed refreshes a feed.
-func RefreshFeed(store *storage.Storage, userID, feedID int64) error {
+func RefreshFeed(store *storage.Storage, userID, feedID int64, forceRefresh bool) error {
 	defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[RefreshFeed] feedID=%d", feedID))
 	user, storeErr := store.UserByID(userID)
 	if storeErr != nil {
@@ -173,10 +173,11 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64) error {
 		}
 
 		originalFeed.Entries = updatedFeed.Entries
-		processor.ProcessFeedEntries(store, originalFeed, user)
+		processor.ProcessFeedEntries(store, originalFeed, user, forceRefresh)
 
-		// We don't update existing entries when the crawler is enabled (we crawl only inexisting entries).
-		if storeErr := store.RefreshFeedEntries(originalFeed.UserID, originalFeed.ID, originalFeed.Entries, !originalFeed.Crawler); storeErr != nil {
+		// We don't update existing entries when the crawler is enabled (we crawl only inexisting entries). Unless it is forced to refresh
+		updateExistingEntries := forceRefresh || !originalFeed.Crawler
+		if storeErr := store.RefreshFeedEntries(originalFeed.UserID, originalFeed.ID, originalFeed.Entries, updateExistingEntries); storeErr != nil {
 			originalFeed.WithError(storeErr.Error())
 			store.UpdateFeedError(originalFeed)
 			return storeErr

+ 2 - 2
reader/processor/processor.go

@@ -38,7 +38,7 @@ var (
 )
 
 // ProcessFeedEntries downloads original web page for entries and apply filters.
-func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.User) {
+func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.User, forceRefresh bool) {
 	var filteredEntries model.Entries
 
 	// array used for bulk push
@@ -56,7 +56,7 @@ func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.Us
 
 		url := getUrlFromEntry(feed, entry)
 		entryIsNew := !store.EntryURLExists(feed.ID, entry.URL)
-		if feed.Crawler && entryIsNew {
+		if feed.Crawler && (entryIsNew || forceRefresh) {
 			logger.Debug("[Processor] Crawling entry %q from feed %q", url, feed.FeedURL)
 
 			startTime := time.Now()

+ 8 - 1
template/templates/views/edit_feed.html

@@ -11,7 +11,14 @@
             <a href="{{ route "feedEntries" "feedID" .feed.ID }}">{{ icon "entries" }}{{ t "menu.feed_entries" }}</a>
         </li>
         <li>
-            <a href="{{ route "refreshFeed" "feedID" .feed.ID }}">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</a>
+            <a href="#"
+                data-confirm="true"
+                data-label-question="{{ t "confirm.question.refresh" }}"
+                data-label-yes="{{ t "confirm.yes" }}"
+                data-label-no="{{ t "confirm.no" }}"
+                data-label-loading="{{ t "confirm.loading" }}"
+                data-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=true"
+                data-no-action-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=false">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</a>
         </li>
     </ul>
 </section>

+ 8 - 1
template/templates/views/feed_entries.html

@@ -37,7 +37,14 @@
         </li>
         {{ end }}
         <li>
-            <a href="{{ route "refreshFeed" "feedID" .feed.ID }}">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</a>
+            <a href="#"
+                data-confirm="true"
+                data-label-question="{{ t "confirm.question.refresh" }}"
+                data-label-yes="{{ t "confirm.yes" }}"
+                data-label-no="{{ t "confirm.no" }}"
+                data-label-loading="{{ t "confirm.loading" }}"
+                data-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=true"
+                data-no-action-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=false">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</a>
         </li>
         <li>
             <a href="{{ route "editFeed" "feedID" .feed.ID }}">{{ icon "edit" }}{{ t "menu.edit_feed" }}</a>

+ 2 - 1
ui/feed_refresh.go

@@ -15,7 +15,8 @@ import (
 
 func (h *handler) refreshFeed(w http.ResponseWriter, r *http.Request) {
 	feedID := request.RouteInt64Param(r, "feedID")
-	if err := feedHandler.RefreshFeed(h.store, request.UserID(r), feedID); err != nil {
+	forceRefresh := request.QueryBoolParam(r, "forceRefresh", false)
+	if err := feedHandler.RefreshFeed(h.store, request.UserID(r), feedID, forceRefresh); err != nil {
 		logger.Error("[UI:RefreshFeed] %v", err)
 	}
 

+ 20 - 8
ui/static/js/app.js

@@ -561,18 +561,22 @@ function handleConfirmationMessage(linkElement, callback) {
     let containerElement = linkElement.parentNode;
     let questionElement = document.createElement("span");
 
-    let yesElement = document.createElement("a");
-    yesElement.href = "#";
-    yesElement.appendChild(document.createTextNode(linkElement.dataset.labelYes));
-    yesElement.onclick = (event) => {
-        event.preventDefault();
-
+    function createLoadingElement() {
         let loadingElement = document.createElement("span");
         loadingElement.className = "loading";
         loadingElement.appendChild(document.createTextNode(linkElement.dataset.labelLoading));
 
         questionElement.remove();
         containerElement.appendChild(loadingElement);
+    }
+
+    let yesElement = document.createElement("a");
+    yesElement.href = "#";
+    yesElement.appendChild(document.createTextNode(linkElement.dataset.labelYes));
+    yesElement.onclick = (event) => {
+        event.preventDefault();
+
+        createLoadingElement();
 
         callback(linkElement.dataset.url, linkElement.dataset.redirectUrl);
     };
@@ -582,8 +586,16 @@ function handleConfirmationMessage(linkElement, callback) {
     noElement.appendChild(document.createTextNode(linkElement.dataset.labelNo));
     noElement.onclick = (event) => {
         event.preventDefault();
-        linkElement.style.display = "inline";
-        questionElement.remove();
+
+        const noActionUrl = linkElement.dataset.noActionUrl;
+        if (noActionUrl) {
+            createLoadingElement();
+
+            callback(noActionUrl, linkElement.dataset.redirectUrl);
+        } else {
+            linkElement.style.display = "inline";
+            questionElement.remove();
+        }
     };
 
     questionElement.className = "confirm";

+ 3 - 1
ui/static/js/bootstrap.js

@@ -59,9 +59,11 @@ document.addEventListener("DOMContentLoaded", function () {
     onClick("a[data-confirm]", (event) => handleConfirmationMessage(event.target, (url, redirectURL) => {
         let request = new RequestBuilder(url);
 
-        request.withCallback(() => {
+        request.withCallback((response) => {
             if (redirectURL) {
                 window.location.href = redirectURL;
+            } else if (response && response.redirected && response.url) {
+                window.location.href = response.url;
             } else {
                 window.location.reload();
             }

+ 2 - 1
ui/ui.go

@@ -66,7 +66,8 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
 	uiRouter.HandleFunc("/feeds/refresh", handler.refreshAllFeeds).Name("refreshAllFeeds").Methods(http.MethodGet)
 
 	// Individual feed pages.
-	uiRouter.HandleFunc("/feed/{feedID}/refresh", handler.refreshFeed).Name("refreshFeed").Methods(http.MethodGet)
+	uiRouter.HandleFunc("/feed/{feedID}/refresh", handler.refreshFeed).Name("refreshFeed").Methods(http.MethodGet, http.MethodPost)
+	uiRouter.HandleFunc("/feed/{feedID}/refresh", handler.refreshFeed).Queries("forceRefresh", "{forceRefresh:true|false}").Name("refreshFeed").Methods(http.MethodGet, http.MethodPost)
 	uiRouter.HandleFunc("/feed/{feedID}/edit", handler.showEditFeedPage).Name("editFeed").Methods(http.MethodGet)
 	uiRouter.HandleFunc("/feed/{feedID}/remove", handler.removeFeed).Name("removeFeed").Methods(http.MethodPost)
 	uiRouter.HandleFunc("/feed/{feedID}/update", handler.updateFeed).Name("updateFeed").Methods(http.MethodPost)

+ 1 - 1
worker/worker.go

@@ -29,7 +29,7 @@ func (w *Worker) Run(c chan model.Job) {
 		logger.Debug("[Worker #%d] Received feed #%d for user #%d", w.id, job.FeedID, job.UserID)
 
 		startTime := time.Now()
-		refreshErr := feedHandler.RefreshFeed(w.store, job.UserID, job.FeedID)
+		refreshErr := feedHandler.RefreshFeed(w.store, job.UserID, job.FeedID, false)
 
 		if config.Opts.HasMetricsCollector() {
 			status := "success"