mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Validates url when adding a link
This commit is contained in:
BIN
Binary file not shown.
@@ -120,6 +120,7 @@
|
||||
/* Validation Messages */
|
||||
"alias_already_exists" = "Alias already exists";
|
||||
"alias_invalid_characters" = "Alias contains invalid characters";
|
||||
"invalid_url_format" = "Please enter a valid URL (e.g., https://example.com)";
|
||||
"tag_already_exists" = "Tag already exists";
|
||||
|
||||
/* Error Messages */
|
||||
|
||||
@@ -140,3 +140,9 @@
|
||||
/* Basic Information */
|
||||
"basic_information" = "基本信息";
|
||||
"actions" = "操作";
|
||||
|
||||
/* Validation Messages */
|
||||
"alias_already_exists" = "别名已存在";
|
||||
"alias_invalid_characters" = "别名包含无效字符";
|
||||
"invalid_url_format" = "请输入有效的网址(例如:https://example.com)";
|
||||
"tag_already_exists" = "标签已存在";
|
||||
|
||||
@@ -16,7 +16,10 @@ struct LinkFormView: View {
|
||||
@State private var tagSearchText: String = ""
|
||||
@State private var isValidatingAlias: Bool = false
|
||||
@State private var aliasValidationMessage: String = ""
|
||||
@State private var urlValidationMessage: String = ""
|
||||
@State private var showingTagPicker: Bool = false
|
||||
@State private var saveErrorMessage: String = ""
|
||||
@State private var showingSaveError: Bool = false
|
||||
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(keyPath: \Tag.name, ascending: true)],
|
||||
@@ -38,7 +41,8 @@ struct LinkFormView: View {
|
||||
var canSave: Bool {
|
||||
return !alias.isEmpty &&
|
||||
(selectedLinkType == .custom ? !text.isEmpty : !originalURL.isEmpty) &&
|
||||
aliasValidationMessage.isEmpty
|
||||
aliasValidationMessage.isEmpty &&
|
||||
urlValidationMessage.isEmpty
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -68,6 +72,14 @@ struct LinkFormView: View {
|
||||
}
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
.onChange(of: selectedLinkType) { _, newValue in
|
||||
// Re-validate URL when link type changes
|
||||
if newValue != .custom {
|
||||
validateURL(originalURL)
|
||||
} else {
|
||||
urlValidationMessage = ""
|
||||
}
|
||||
}
|
||||
|
||||
// URL or Text field based on type
|
||||
if selectedLinkType == .custom {
|
||||
@@ -84,6 +96,24 @@ struct LinkFormView: View {
|
||||
.autocapitalization(.none)
|
||||
.autocorrectionDisabled()
|
||||
.keyboardType(.URL)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(!urlValidationMessage.isEmpty ? Color.orange : Color.clear, lineWidth: 2)
|
||||
)
|
||||
.onChange(of: originalURL) { _, newValue in
|
||||
validateURL(newValue)
|
||||
}
|
||||
|
||||
if !urlValidationMessage.isEmpty {
|
||||
HStack {
|
||||
Image(systemName: "exclamationmark.triangle")
|
||||
.foregroundColor(.orange)
|
||||
Text(urlValidationMessage)
|
||||
.font(.caption)
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
}
|
||||
|
||||
if selectedLinkType == .template {
|
||||
Text("template_help")
|
||||
@@ -169,6 +199,14 @@ struct LinkFormView: View {
|
||||
.onAppear {
|
||||
setupForm()
|
||||
}
|
||||
.alert("Error", isPresented: $showingSaveError) {
|
||||
Button("OK") {
|
||||
showingSaveError = false
|
||||
saveErrorMessage = ""
|
||||
}
|
||||
} message: {
|
||||
Text(saveErrorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupForm() {
|
||||
@@ -179,6 +217,10 @@ struct LinkFormView: View {
|
||||
linkDescription = link.linkDescription ?? ""
|
||||
selectedLinkType = link.linkTypeEnum
|
||||
selectedTags = link.sortedTags
|
||||
|
||||
// Validate existing data
|
||||
validateAlias(alias)
|
||||
validateURL(originalURL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +256,62 @@ struct LinkFormView: View {
|
||||
isValidatingAlias = false
|
||||
}
|
||||
|
||||
private func validateURL(_ url: String) {
|
||||
// Don't validate empty URLs (allow empty for templates with placeholders)
|
||||
if url.isEmpty {
|
||||
urlValidationMessage = ""
|
||||
return
|
||||
}
|
||||
|
||||
// For templates, allow URLs with placeholders like {query}
|
||||
if selectedLinkType == .template {
|
||||
// Check if it's a valid URL pattern with or without placeholders
|
||||
let urlWithoutPlaceholders = url.replacingOccurrences(of: #"\{[^}]+\}"#, with: "placeholder", options: .regularExpression)
|
||||
if isValidURL(urlWithoutPlaceholders) {
|
||||
urlValidationMessage = ""
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// For regular links, validate the URL strictly
|
||||
if selectedLinkType == .link {
|
||||
if isValidURL(url) {
|
||||
urlValidationMessage = ""
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, the URL is invalid
|
||||
urlValidationMessage = NSLocalizedString("invalid_url_format", comment: "Invalid URL format")
|
||||
}
|
||||
|
||||
private func isValidURL(_ urlString: String) -> Bool {
|
||||
// First check if it's a valid URL
|
||||
guard let url = URL(string: urlString) else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if it has a scheme
|
||||
guard let scheme = url.scheme else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Allow http, https, and other common schemes
|
||||
let allowedSchemes = ["http", "https", "ftp", "mailto", "tel", "sms"]
|
||||
if !allowedSchemes.contains(scheme.lowercased()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if it has a host for http/https URLs
|
||||
if ["http", "https"].contains(scheme.lowercased()) {
|
||||
guard let host = url.host, !host.isEmpty else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private func toggleTag(_ tag: Tag) {
|
||||
if selectedTags.contains(where: { $0.id == tag.id }) {
|
||||
selectedTags.removeAll { $0.id == tag.id }
|
||||
@@ -250,6 +348,23 @@ struct LinkFormView: View {
|
||||
}
|
||||
|
||||
private func saveLink() {
|
||||
// Clear any previous error
|
||||
saveErrorMessage = ""
|
||||
showingSaveError = false
|
||||
|
||||
// Validate before saving
|
||||
if !aliasValidationMessage.isEmpty {
|
||||
saveErrorMessage = aliasValidationMessage
|
||||
showingSaveError = true
|
||||
return
|
||||
}
|
||||
|
||||
if !urlValidationMessage.isEmpty && selectedLinkType != .custom {
|
||||
saveErrorMessage = urlValidationMessage
|
||||
showingSaveError = true
|
||||
return
|
||||
}
|
||||
|
||||
let success: Bool
|
||||
|
||||
if isEditing, let link = link {
|
||||
@@ -275,6 +390,13 @@ struct LinkFormView: View {
|
||||
|
||||
if success {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} else {
|
||||
// Show error from view model
|
||||
if let errorMessage = linkViewModel.errorMessage {
|
||||
saveErrorMessage = errorMessage
|
||||
showingSaveError = true
|
||||
linkViewModel.errorMessage = nil // Clear the error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user