Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions EhPanda/App/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1892,6 +1892,24 @@ internal enum L10n {
internal static let success = L10n.tr("Localizable", "hud.title.success", fallback: "Success")
}
}
internal enum DateJumpView {
internal enum Button {
/// Seek Newer
internal static let seekNewer = L10n.tr("Localizable", "date_jump_view.button.seek_newer", fallback: "Seek Newer")
/// Seek Older
internal static let seekOlder = L10n.tr("Localizable", "date_jump_view.button.seek_older", fallback: "Seek Older")
}
internal enum Footer {
/// Seek to galleries around the selected date.
internal static let seekAroundDate = L10n.tr("Localizable", "date_jump_view.footer.seek_around_date", fallback: "Seek to galleries around the selected date.")
}
internal enum Title {
/// Date
internal static let date = L10n.tr("Localizable", "date_jump_view.title.date", fallback: "Date")
/// Date Jump
internal static let dateJump = L10n.tr("Localizable", "date_jump_view.title.date_jump", fallback: "Date Jump")
}
}
internal enum JumpPageView {
internal enum Button {
/// Confirm
Expand Down Expand Up @@ -2160,6 +2178,8 @@ internal enum L10n {
}
internal enum ToolbarItem {
internal enum Button {
/// Date Jump
internal static let dateJump = L10n.tr("Localizable", "toolbar_item.button.date_jump", fallback: "Date Jump")
/// Filters
internal static let filters = L10n.tr("Localizable", "toolbar_item.button.filters", fallback: "Filters")
/// Jump page
Expand Down
61 changes: 61 additions & 0 deletions EhPanda/App/Tools/Extensions/AlertKit_Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,64 @@ private struct JumpPageAlert<Content: View>: View {
.synchronize($isPresented, $manager.isPresented)
}
}

struct DateJumpView: View {
let pageNumber: PageNumber
@Binding var selectedDate: Date
let jumpAction: (PageJumpDirection) -> Void

private var navigation: PageJumpNavigation? {
pageNumber.jumpNavigation
}
private var dateRange: ClosedRange<Date> {
navigation?.dateRange ?? Date.distantPast...Date.distantFuture
}
private var showsNewerButton: Bool {
navigation?.previousURL != nil
}
private var showsOlderButton: Bool {
navigation?.nextURL != nil
}

var body: some View {
NavigationView {
Form {
Section {
DatePicker(
L10n.Localizable.DateJumpView.Title.date,
selection: $selectedDate,
in: dateRange,
displayedComponents: .date
)
.datePickerStyle(.graphical)
} footer: {
Text(L10n.Localizable.DateJumpView.Footer.seekAroundDate)
}

Section {
if showsNewerButton {
Button {
jumpAction(.newer)
} label: {
Label(L10n.Localizable.DateJumpView.Button.seekNewer, systemImage: "chevron.left")
}
}
if showsOlderButton {
Button {
jumpAction(.older)
} label: {
Label(L10n.Localizable.DateJumpView.Button.seekOlder, systemImage: "chevron.right")
}
}
}
}
.navigationTitle(L10n.Localizable.DateJumpView.Title.dateJump)
.navigationBarTitleDisplayMode(.inline)
}
.onAppear {
if let navigation {
selectedDate = navigation.clampedDate(selectedDate)
}
}
}
}
84 changes: 81 additions & 3 deletions EhPanda/App/Tools/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,73 @@ extension Parser {

// MARK: PageNumber
static func parsePageNum(doc: HTMLDocument) -> PageNumber {
func parseScriptVariable(name: String) -> String? {
let escapedName = NSRegularExpression.escapedPattern(for: name)
let pattern = #"var\s+\#(escapedName)\s*=\s*["']([^"']*)["']\s*;"#
guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }

for script in doc.xpath("//script") {
guard let text = script.text else { continue }
let range = NSRange(text.startIndex..., in: text)
guard let match = regex.firstMatch(in: text, range: range),
let valueRange = Range(match.range(at: 1), in: text)
else { continue }

return String(text[valueRange])
}
return nil
}
func parseScriptURL(name: String) -> URL? {
guard var value = parseScriptVariable(name: name)?.trimmingCharacters(in: .whitespacesAndNewlines),
!value.isEmpty
else { return nil }
value = value
.replacingOccurrences(of: "&amp;", with: "&")
.replacingOccurrences(of: "\\u0026", with: "&")

let baseURL = Defaults.URL.host
let parsedURL: URL?
if let url = URL(string: value), url.scheme != nil {
parsedURL = url
} else {
parsedURL = URL(string: value, relativeTo: baseURL)?.absoluteURL
}

guard let parsedURL else { return nil }
guard var components = URLComponents(url: parsedURL, resolvingAgainstBaseURL: false) else {
return parsedURL
}

let knownGalleryHosts = [
Defaults.URL.ehentai.host,
Defaults.URL.exhentai.host,
Defaults.URL.sexhentai.host
]
.compactMap { $0?.lowercased() }

if let host = components.host?.lowercased(),
knownGalleryHosts.contains(host),
let baseComponents = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)
{
components.scheme = baseComponents.scheme
components.host = baseComponents.host
}
return components.url
}
func parseScriptDate(name: String) -> Date? {
guard let value = parseScriptVariable(name: name), !value.isEmpty else { return nil }
return try? parseDate(time: value, format: "yyyy-MM-dd")
}
func parsePageJumpNavigation() -> PageJumpNavigation? {
let navigation = PageJumpNavigation(
previousURL: parseScriptURL(name: "prevurl"),
nextURL: parseScriptURL(name: "nexturl"),
minimumDate: parseScriptDate(name: "mindate"),
maximumDate: parseScriptDate(name: "maxdate")
)
return navigation.isEnabled ? navigation : nil
}

var current = 0
var maximum = 0

Expand All @@ -1402,9 +1469,16 @@ extension Parser {
break
}

return PageNumber(lastItemTimestamp: timestamp, isNextButtonEnabled: isEnabled)
return PageNumber(
lastItemTimestamp: timestamp,
isNextButtonEnabled: isEnabled,
jumpNavigation: parsePageJumpNavigation()
)
} else {
return PageNumber(isNextButtonEnabled: false)
return PageNumber(
isNextButtonEnabled: false,
jumpNavigation: parsePageJumpNavigation()
)
}
}

Expand All @@ -1418,7 +1492,11 @@ extension Parser {
maximum = num - 1
}
}
return PageNumber(current: current, maximum: maximum)
return PageNumber(
current: current,
maximum: maximum,
jumpNavigation: parsePageJumpNavigation()
)
}

// MARK: SortOrder
Expand Down
7 changes: 7 additions & 0 deletions EhPanda/App/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@
// MARK: ToolbarItem
"toolbar_item.button.filters" = "Filters";
"toolbar_item.button.jump_page" = "Jump page";
"toolbar_item.button.date_jump" = "Datumssprung";
"toolbar_item.button.quick_search" = "Quick search";

// MARK: DateJump
"date_jump_view.title.date_jump" = "Datumssprung";
"date_jump_view.title.date" = "Datum";
"date_jump_view.footer.seek_around_date" = "Zu Galerien um das ausgewählte Datum springen.";
"date_jump_view.button.seek_newer" = "Zu neueren springen";
"date_jump_view.button.seek_older" = "Zu älteren springen";
// MARK: JumpPage
"jump_page_view.title.jump_page" = "Jump page";
"jump_page_view.button.confirm" = "Confirm";
Expand Down
7 changes: 7 additions & 0 deletions EhPanda/App/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@
// MARK: ToolbarItem
"toolbar_item.button.filters" = "Filters";
"toolbar_item.button.jump_page" = "Jump page";
"toolbar_item.button.date_jump" = "Date Jump";
"toolbar_item.button.quick_search" = "Quick search";

// MARK: DateJump
"date_jump_view.title.date_jump" = "Date Jump";
"date_jump_view.title.date" = "Date";
"date_jump_view.footer.seek_around_date" = "Seek to galleries around the selected date.";
"date_jump_view.button.seek_newer" = "Seek Newer";
"date_jump_view.button.seek_older" = "Seek Older";
// MARK: JumpPage
"jump_page_view.title.jump_page" = "Jump page";
"jump_page_view.button.confirm" = "Confirm";
Expand Down
7 changes: 7 additions & 0 deletions EhPanda/App/ja.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@
// MARK: ToolbarItem
"toolbar_item.button.filters" = "フィルター";
"toolbar_item.button.jump_page" = "ページジャンプ";
"toolbar_item.button.date_jump" = "日付ジャンプ";
"toolbar_item.button.quick_search" = "クイック検索";

// MARK: DateJump
"date_jump_view.title.date_jump" = "日付ジャンプ";
"date_jump_view.title.date" = "日付";
"date_jump_view.footer.seek_around_date" = "選択した日付付近のギャラリーへ移動します。";
"date_jump_view.button.seek_newer" = "新しい方へ移動";
"date_jump_view.button.seek_older" = "古い方へ移動";
// MARK: JumpPage
"jump_page_view.title.jump_page" = "ページジャンプ";
"jump_page_view.button.confirm" = "確認";
Expand Down
7 changes: 7 additions & 0 deletions EhPanda/App/ko.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@
// MARK: ToolbarItem
"toolbar_item.button.filters" = "필터";
"toolbar_item.button.jump_page" = "페이지 이동";
"toolbar_item.button.date_jump" = "날짜 이동";
"toolbar_item.button.quick_search" = "빠른 검색";

// MARK: DateJump
"date_jump_view.title.date_jump" = "날짜 이동";
"date_jump_view.title.date" = "날짜";
"date_jump_view.footer.seek_around_date" = "선택한 날짜 근처의 갤러리로 이동합니다.";
"date_jump_view.button.seek_newer" = "새 항목으로 이동";
"date_jump_view.button.seek_older" = "오래된 항목으로 이동";
// MARK: JumpPage
"jump_page_view.title.jump_page" = "페이지 이동";
"jump_page_view.button.confirm" = "확인";
Expand Down
7 changes: 7 additions & 0 deletions EhPanda/App/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@
// MARK: ToolbarItem
"toolbar_item.button.filters" = "筛选";
"toolbar_item.button.jump_page" = "页码跳转";
"toolbar_item.button.date_jump" = "日期跳转";
"toolbar_item.button.quick_search" = "快速搜索";

// MARK: DateJump
"date_jump_view.title.date_jump" = "日期跳转";
"date_jump_view.title.date" = "日期";
"date_jump_view.footer.seek_around_date" = "跳转到所选日期附近的画廊。";
"date_jump_view.button.seek_newer" = "跳到较新";
"date_jump_view.button.seek_older" = "跳到较旧";
// MARK: JumpPage
"jump_page_view.title.jump_page" = "页码跳转";
"jump_page_view.button.confirm" = "确认";
Expand Down
7 changes: 7 additions & 0 deletions EhPanda/App/zh-Hant-HK.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@
// MARK: ToolbarItem
"toolbar_item.button.filters" = "過濾";
"toolbar_item.button.jump_page" = "跳到...";
"toolbar_item.button.date_jump" = "日期跳轉";
"toolbar_item.button.quick_search" = "快速搜尋";

// MARK: DateJump
"date_jump_view.title.date_jump" = "日期跳轉";
"date_jump_view.title.date" = "日期";
"date_jump_view.footer.seek_around_date" = "跳轉到所選日期附近的畫廊。";
"date_jump_view.button.seek_newer" = "跳到較新";
"date_jump_view.button.seek_older" = "跳到較舊";
// MARK: JumpPage
"jump_page_view.title.jump_page" = "跳到...";
"jump_page_view.button.confirm" = "確定";
Expand Down
7 changes: 7 additions & 0 deletions EhPanda/App/zh-Hant-TW.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@
// MARK: ToolbarItem
"toolbar_item.button.filters" = "過濾";
"toolbar_item.button.jump_page" = "跳到...";
"toolbar_item.button.date_jump" = "日期跳轉";
"toolbar_item.button.quick_search" = "快速搜尋";

// MARK: DateJump
"date_jump_view.title.date_jump" = "日期跳轉";
"date_jump_view.title.date" = "日期";
"date_jump_view.footer.seek_around_date" = "跳轉到所選日期附近的畫廊。";
"date_jump_view.button.seek_newer" = "跳到較新";
"date_jump_view.button.seek_older" = "跳到較舊";
// MARK: JumpPage
"jump_page_view.title.jump_page" = "跳到...";
"jump_page_view.button.confirm" = "確定";
Expand Down
7 changes: 7 additions & 0 deletions EhPanda/App/zh-Hant.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@
// MARK: ToolbarItem
"toolbar_item.button.filters" = "過濾";
"toolbar_item.button.jump_page" = "跳到...";
"toolbar_item.button.date_jump" = "日期跳轉";
"toolbar_item.button.quick_search" = "快速搜尋";

// MARK: DateJump
"date_jump_view.title.date_jump" = "日期跳轉";
"date_jump_view.title.date" = "日期";
"date_jump_view.footer.seek_around_date" = "跳轉到所選日期附近的畫廊。";
"date_jump_view.button.seek_newer" = "跳到較新";
"date_jump_view.button.seek_older" = "跳到較舊";
// MARK: JumpPage
"jump_page_view.title.jump_page" = "跳到...";
"jump_page_view.button.confirm" = "確定";
Expand Down
49 changes: 49 additions & 0 deletions EhPanda/Models/Support/Misc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,54 @@ import SwiftyBeaver
typealias Logger = SwiftyBeaver
typealias FavoritesSortOrder = EhSetting.FavoritesSortOrder

enum PageJumpDirection: Equatable {
case newer
case older
}

struct PageJumpNavigation: Equatable {
var previousURL: URL?
var nextURL: URL?
var minimumDate: Date?
var maximumDate: Date?

var isEnabled: Bool {
previousURL != nil || nextURL != nil
}
var dateRange: ClosedRange<Date> {
(minimumDate ?? .distantPast)...(maximumDate ?? .distantFuture)
}

func clampedDate(_ date: Date = Date()) -> Date {
if let maximumDate, date > maximumDate {
return maximumDate
}
if let minimumDate, date < minimumDate {
return minimumDate
}
return date
}

func seekURL(date: Date, direction: PageJumpDirection) -> URL? {
let baseURL: URL?
switch direction {
case .newer:
baseURL = previousURL
case .older:
baseURL = nextURL
}
return baseURL?.appending(queryItems: ["seek": Self.dateFormatter.string(from: date)])
}

private static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
}

protocol DateFormattable {
var originalDate: Date { get }
}
Expand All @@ -29,6 +77,7 @@ struct PageNumber: Equatable {
var maximum = 0
var lastItemTimestamp: String?
var isNextButtonEnabled = false
var jumpNavigation: PageJumpNavigation?

var isSinglePage: Bool {
current == 0 && maximum == 0
Expand Down
Loading