settings.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package form // import "miniflux.app/v2/internal/ui/form"
  4. import (
  5. "net/http"
  6. "strconv"
  7. "miniflux.app/v2/internal/locale"
  8. "miniflux.app/v2/internal/model"
  9. )
  10. // MarkReadBehavior list all possible behaviors for automatically marking an entry as read
  11. type MarkReadBehavior string
  12. var (
  13. NoAutoMarkAsRead MarkReadBehavior = "no-auto"
  14. MarkAsReadOnView MarkReadBehavior = "on-view"
  15. MarkAsReadOnViewButWaitForPlayerCompletion MarkReadBehavior = "on-view-but-wait-for-player-completion"
  16. MarkAsReadOnlyOnPlayerCompletion MarkReadBehavior = "on-player-completion"
  17. )
  18. // SettingsForm represents the settings form.
  19. type SettingsForm struct {
  20. Username string
  21. Password string
  22. Confirmation string
  23. Theme string
  24. Language string
  25. Timezone string
  26. EntryDirection string
  27. EntryOrder string
  28. EntriesPerPage int
  29. KeyboardShortcuts bool
  30. ShowReadingTime bool
  31. CustomCSS string
  32. EntrySwipe bool
  33. GestureNav string
  34. DisplayMode string
  35. DefaultReadingSpeed int
  36. CJKReadingSpeed int
  37. DefaultHomePage string
  38. CategoriesSortingOrder string
  39. MarkReadOnView bool
  40. // MarkReadBehavior is a string representation of the MarkReadOnView and MarkReadOnMediaPlayerCompletion fields together
  41. MarkReadBehavior MarkReadBehavior
  42. MediaPlaybackRate float64
  43. BlockFilterEntryRules string
  44. KeepFilterEntryRules string
  45. }
  46. // MarkAsReadBehavior returns the MarkReadBehavior from the given MarkReadOnView and MarkReadOnMediaPlayerCompletion values.
  47. // Useful to convert the values from the User model to the form
  48. func MarkAsReadBehavior(markReadOnView, markReadOnMediaPlayerCompletion bool) MarkReadBehavior {
  49. switch {
  50. case markReadOnView && !markReadOnMediaPlayerCompletion:
  51. return MarkAsReadOnView
  52. case markReadOnView && markReadOnMediaPlayerCompletion:
  53. return MarkAsReadOnViewButWaitForPlayerCompletion
  54. case !markReadOnView && markReadOnMediaPlayerCompletion:
  55. return MarkAsReadOnlyOnPlayerCompletion
  56. case !markReadOnView && !markReadOnMediaPlayerCompletion:
  57. fallthrough // Explicit defaulting
  58. default:
  59. return NoAutoMarkAsRead
  60. }
  61. }
  62. // ExtractMarkAsReadBehavior returns the MarkReadOnView and MarkReadOnMediaPlayerCompletion values from the given MarkReadBehavior.
  63. // Useful to extract the values from the form to the User model
  64. func ExtractMarkAsReadBehavior(behavior MarkReadBehavior) (markReadOnView, markReadOnMediaPlayerCompletion bool) {
  65. switch behavior {
  66. case MarkAsReadOnView:
  67. return true, false
  68. case MarkAsReadOnViewButWaitForPlayerCompletion:
  69. return true, true
  70. case MarkAsReadOnlyOnPlayerCompletion:
  71. return false, true
  72. case NoAutoMarkAsRead:
  73. fallthrough // Explicit defaulting
  74. default:
  75. return false, false
  76. }
  77. }
  78. // Merge updates the fields of the given user.
  79. func (s *SettingsForm) Merge(user *model.User) *model.User {
  80. user.Username = s.Username
  81. user.Theme = s.Theme
  82. user.Language = s.Language
  83. user.Timezone = s.Timezone
  84. user.EntryDirection = s.EntryDirection
  85. user.EntryOrder = s.EntryOrder
  86. user.EntriesPerPage = s.EntriesPerPage
  87. user.KeyboardShortcuts = s.KeyboardShortcuts
  88. user.ShowReadingTime = s.ShowReadingTime
  89. user.Stylesheet = s.CustomCSS
  90. user.EntrySwipe = s.EntrySwipe
  91. user.GestureNav = s.GestureNav
  92. user.DisplayMode = s.DisplayMode
  93. user.CJKReadingSpeed = s.CJKReadingSpeed
  94. user.DefaultReadingSpeed = s.DefaultReadingSpeed
  95. user.DefaultHomePage = s.DefaultHomePage
  96. user.CategoriesSortingOrder = s.CategoriesSortingOrder
  97. user.MediaPlaybackRate = s.MediaPlaybackRate
  98. user.BlockFilterEntryRules = s.BlockFilterEntryRules
  99. user.KeepFilterEntryRules = s.KeepFilterEntryRules
  100. MarkReadOnView, MarkReadOnMediaPlayerCompletion := ExtractMarkAsReadBehavior(s.MarkReadBehavior)
  101. user.MarkReadOnView = MarkReadOnView
  102. user.MarkReadOnMediaPlayerCompletion = MarkReadOnMediaPlayerCompletion
  103. if s.Password != "" {
  104. user.Password = s.Password
  105. }
  106. return user
  107. }
  108. // Validate makes sure the form values are valid.
  109. func (s *SettingsForm) Validate() *locale.LocalizedError {
  110. if s.Username == "" || s.Theme == "" || s.Language == "" || s.Timezone == "" || s.EntryDirection == "" || s.DisplayMode == "" || s.DefaultHomePage == "" {
  111. return locale.NewLocalizedError("error.settings_mandatory_fields")
  112. }
  113. if s.CJKReadingSpeed <= 0 || s.DefaultReadingSpeed <= 0 {
  114. return locale.NewLocalizedError("error.settings_reading_speed_is_positive")
  115. }
  116. if s.Confirmation == "" {
  117. // Firefox insists on auto-completing the password field.
  118. // If the confirmation field is blank, the user probably
  119. // didn't intend to change their password.
  120. s.Password = ""
  121. } else if s.Password != "" {
  122. if s.Password != s.Confirmation {
  123. return locale.NewLocalizedError("error.different_passwords")
  124. }
  125. }
  126. if s.MediaPlaybackRate < 0.25 || s.MediaPlaybackRate > 4 {
  127. return locale.NewLocalizedError("error.settings_media_playback_rate_range")
  128. }
  129. return nil
  130. }
  131. // NewSettingsForm returns a new SettingsForm.
  132. func NewSettingsForm(r *http.Request) *SettingsForm {
  133. entriesPerPage, err := strconv.ParseInt(r.FormValue("entries_per_page"), 10, 0)
  134. if err != nil {
  135. entriesPerPage = 0
  136. }
  137. defaultReadingSpeed, err := strconv.ParseInt(r.FormValue("default_reading_speed"), 10, 0)
  138. if err != nil {
  139. defaultReadingSpeed = 0
  140. }
  141. cjkReadingSpeed, err := strconv.ParseInt(r.FormValue("cjk_reading_speed"), 10, 0)
  142. if err != nil {
  143. cjkReadingSpeed = 0
  144. }
  145. mediaPlaybackRate, err := strconv.ParseFloat(r.FormValue("media_playback_rate"), 64)
  146. if err != nil {
  147. mediaPlaybackRate = 1
  148. }
  149. return &SettingsForm{
  150. Username: r.FormValue("username"),
  151. Password: r.FormValue("password"),
  152. Confirmation: r.FormValue("confirmation"),
  153. Theme: r.FormValue("theme"),
  154. Language: r.FormValue("language"),
  155. Timezone: r.FormValue("timezone"),
  156. EntryDirection: r.FormValue("entry_direction"),
  157. EntryOrder: r.FormValue("entry_order"),
  158. EntriesPerPage: int(entriesPerPage),
  159. KeyboardShortcuts: r.FormValue("keyboard_shortcuts") == "1",
  160. ShowReadingTime: r.FormValue("show_reading_time") == "1",
  161. CustomCSS: r.FormValue("custom_css"),
  162. EntrySwipe: r.FormValue("entry_swipe") == "1",
  163. GestureNav: r.FormValue("gesture_nav"),
  164. DisplayMode: r.FormValue("display_mode"),
  165. DefaultReadingSpeed: int(defaultReadingSpeed),
  166. CJKReadingSpeed: int(cjkReadingSpeed),
  167. DefaultHomePage: r.FormValue("default_home_page"),
  168. CategoriesSortingOrder: r.FormValue("categories_sorting_order"),
  169. MarkReadOnView: r.FormValue("mark_read_on_view") == "1",
  170. MarkReadBehavior: MarkReadBehavior(r.FormValue("mark_read_behavior")),
  171. MediaPlaybackRate: mediaPlaybackRate,
  172. BlockFilterEntryRules: r.FormValue("block_filter_entry_rules"),
  173. KeepFilterEntryRules: r.FormValue("keep_filter_entry_rules"),
  174. }
  175. }