mirror of
https://github.com/wahyd4/links.git
synced 2026-08-08 21:04:53 +10:00
template works
This commit is contained in:
BIN
Binary file not shown.
@@ -29,6 +29,8 @@
|
||||
"link_type" = "Link Type";
|
||||
"markdown_content" = "Markdown Content";
|
||||
"template_help" = "Use {parameter} or {parameter,default=value} for templates";
|
||||
"invalid_template_format" = "Invalid template format. Use {parameter} or {parameter,default=value}";
|
||||
"invalid_base_url_format" = "Invalid base URL format";
|
||||
|
||||
/* Tags */
|
||||
"tags" = "Tags";
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
"link_type" = "链接类型";
|
||||
"markdown_content" = "Markdown 内容";
|
||||
"template_help" = "使用 {参数} 或 {参数,default=值} 创建模板";
|
||||
"invalid_template_format" = "无效的模板格式。请使用 {参数} 或 {参数,default=值}";
|
||||
"invalid_base_url_format" = "无效的基础网址格式";
|
||||
|
||||
/* Tags */
|
||||
"tags" = "标签";
|
||||
|
||||
@@ -6,9 +6,19 @@ struct URLTemplateProcessor {
|
||||
let extractedParams = extractParameters(from: template)
|
||||
|
||||
for param in extractedParams {
|
||||
let placeholder = "{\(param.name)" + (param.defaultValue != nil ? ",default=\(param.defaultValue!)" : "") + "}"
|
||||
// Create patterns to match both formats: {name} and {name, default=value}
|
||||
let patterns = [
|
||||
"\\{\(NSRegularExpression.escapedPattern(for: param.name))\\}",
|
||||
"\\{\(NSRegularExpression.escapedPattern(for: param.name))\\s*,\\s*default\\s*=\\s*\(NSRegularExpression.escapedPattern(for: param.defaultValue ?? ""))\\}"
|
||||
]
|
||||
|
||||
let value = parameters[param.name] ?? param.defaultValue ?? ""
|
||||
processedURL = processedURL.replacingOccurrences(of: placeholder, with: value)
|
||||
|
||||
for pattern in patterns {
|
||||
if let regex = try? NSRegularExpression(pattern: pattern, options: []) {
|
||||
processedURL = regex.stringByReplacingMatches(in: processedURL, options: [], range: NSRange(location: 0, length: processedURL.utf16.count), withTemplate: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return processedURL
|
||||
@@ -16,7 +26,8 @@ struct URLTemplateProcessor {
|
||||
|
||||
static func extractParameters(from template: String) -> [TemplateParameter] {
|
||||
var parameters: [TemplateParameter] = []
|
||||
let pattern = "\\{([^,}]+)(?:,default=([^}]+))?\\}"
|
||||
// Updated pattern to handle optional spaces around comma and default=
|
||||
let pattern = "\\{([^,}]+)(?:\\s*,\\s*default\\s*=\\s*([^}]+))?\\}"
|
||||
|
||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
|
||||
return parameters
|
||||
@@ -29,12 +40,12 @@ struct URLTemplateProcessor {
|
||||
let defaultRange = match.range(at: 2)
|
||||
|
||||
if let nameSwiftRange = Range(nameRange, in: template) {
|
||||
let name = String(template[nameSwiftRange])
|
||||
let name = String(template[nameSwiftRange]).trimmingCharacters(in: .whitespaces)
|
||||
var defaultValue: String? = nil
|
||||
|
||||
if defaultRange.location != NSNotFound,
|
||||
let defaultSwiftRange = Range(defaultRange, in: template) {
|
||||
defaultValue = String(template[defaultSwiftRange])
|
||||
defaultValue = String(template[defaultSwiftRange]).trimmingCharacters(in: .whitespaces)
|
||||
}
|
||||
|
||||
parameters.append(TemplateParameter(name: name, defaultValue: defaultValue))
|
||||
|
||||
@@ -45,6 +45,44 @@ struct LinkFormView: View {
|
||||
urlValidationMessage.isEmpty
|
||||
}
|
||||
|
||||
var templateValidationView: some View {
|
||||
Group {
|
||||
if selectedLinkType == .template && !originalURL.isEmpty {
|
||||
let isValidTemplate = URLTemplateProcessor.validateTemplate(originalURL)
|
||||
let hasParameters = URLTemplateProcessor.hasParameters(originalURL)
|
||||
|
||||
if isValidTemplate && hasParameters {
|
||||
// Valid template with parameters - show success with iOS blue
|
||||
HStack {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.blue)
|
||||
Text("Valid template with parameters")
|
||||
.font(.caption)
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
} else if !hasParameters {
|
||||
// No parameters detected - show warning
|
||||
HStack {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundColor(.orange)
|
||||
Text("Template should contain parameters like {query} or {query,default=value}")
|
||||
.font(.caption)
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
} else {
|
||||
// Invalid template - show error
|
||||
HStack {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.foregroundColor(.red)
|
||||
Text("Invalid template syntax. Use {parameter} or {parameter,default=value}")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
@@ -99,7 +137,7 @@ struct LinkFormView: View {
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(!urlValidationMessage.isEmpty ? Color.orange : Color.clear, lineWidth: 2)
|
||||
.stroke(getBorderColor(), lineWidth: 2)
|
||||
)
|
||||
.onChange(of: originalURL) { _, newValue in
|
||||
validateURL(newValue)
|
||||
@@ -116,9 +154,27 @@ struct LinkFormView: View {
|
||||
}
|
||||
|
||||
if selectedLinkType == .template {
|
||||
Text("template_help")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
// Template help text
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Template type allows you to create dynamic URLs with parameters. Use {query, default=value} syntax in the URL.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text("Example: https://google.com/search?q={query, default=hello}")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Color.gray.opacity(0.1))
|
||||
.cornerRadius(4)
|
||||
}
|
||||
|
||||
// Template validation status
|
||||
if !originalURL.isEmpty {
|
||||
templateValidationView
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,12 +319,21 @@ struct LinkFormView: View {
|
||||
return
|
||||
}
|
||||
|
||||
// For templates, allow URLs with placeholders like {query}
|
||||
// For templates, use URLTemplateProcessor for validation
|
||||
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 = ""
|
||||
// Check if it's a valid template
|
||||
if URLTemplateProcessor.validateTemplate(url) {
|
||||
// Check if the base URL (without parameters) is valid
|
||||
let urlWithoutPlaceholders = url.replacingOccurrences(of: #"\{[^}]+\}"#, with: "placeholder", options: .regularExpression)
|
||||
if isValidURL(urlWithoutPlaceholders) {
|
||||
urlValidationMessage = ""
|
||||
return
|
||||
} else {
|
||||
urlValidationMessage = NSLocalizedString("invalid_base_url_format", comment: "Invalid base URL format")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
urlValidationMessage = NSLocalizedString("invalid_template_format", comment: "Invalid template format")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -312,6 +377,25 @@ struct LinkFormView: View {
|
||||
return true
|
||||
}
|
||||
|
||||
private func getBorderColor() -> Color {
|
||||
// Show error state if there's a validation message
|
||||
if !urlValidationMessage.isEmpty {
|
||||
return .orange
|
||||
}
|
||||
|
||||
// For template links, show blue if valid template with parameters
|
||||
if selectedLinkType == .template && !originalURL.isEmpty {
|
||||
let isValidTemplate = URLTemplateProcessor.validateTemplate(originalURL)
|
||||
let hasParameters = URLTemplateProcessor.hasParameters(originalURL)
|
||||
|
||||
if isValidTemplate && hasParameters {
|
||||
return .blue
|
||||
}
|
||||
}
|
||||
|
||||
return .clear
|
||||
}
|
||||
|
||||
private func toggleTag(_ tag: Tag) {
|
||||
if selectedTags.contains(where: { $0.id == tag.id }) {
|
||||
selectedTags.removeAll { $0.id == tag.id }
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# Template Link Enhancement Implementation
|
||||
|
||||
This document summarizes the enhancements made to the iOS app's template link functionality.
|
||||
|
||||
## Features Implemented
|
||||
|
||||
### 1. Enhanced Template Help Text
|
||||
- **Location**: LinkFormView.swift (when template type is selected)
|
||||
- **Content**: Added detailed explanation with example
|
||||
```
|
||||
Template type allows you to create dynamic URLs with parameters. Use {query, default=value} syntax in the URL.
|
||||
|
||||
Example: https://google.com/search?q={query, default=hello}
|
||||
```
|
||||
|
||||
### 2. Real-time Template Validation
|
||||
- **Visual feedback with iOS blue highlighting** when template is valid
|
||||
- **Warning messages** when template has no parameters
|
||||
- **Error messages** when template syntax is invalid
|
||||
|
||||
### 3. Border Color Indicators
|
||||
- **Blue border**: Valid template with parameters
|
||||
- **Orange border**: Invalid URL or template syntax error
|
||||
- **Clear border**: Normal state
|
||||
|
||||
### 4. Template Validation States
|
||||
- ✅ **Valid with parameters**: Shows blue checkmark with "Valid template with parameters"
|
||||
- ⚠️ **No parameters**: Shows orange warning "Template should contain parameters like {query} or {query,default=value}"
|
||||
- ❌ **Invalid syntax**: Shows red error "Invalid template syntax. Use {parameter} or {parameter,default=value}"
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### URLTemplateProcessor Enhancements
|
||||
- **Updated regex pattern** to handle flexible spacing: `{query, default=value}` vs `{query,default=value}`
|
||||
- **Improved parameter extraction** with whitespace trimming
|
||||
- **Enhanced validation** using existing processor methods
|
||||
|
||||
### LinkFormView Changes
|
||||
- **New computed property**: `templateValidationView` for real-time feedback
|
||||
- **Enhanced border styling**: `getBorderColor()` method for dynamic colors
|
||||
- **Improved URL validation**: Better template-specific validation logic
|
||||
|
||||
### Localization Updates
|
||||
- **English**: Added `invalid_template_format` and `invalid_base_url_format`
|
||||
- **Chinese**: Added corresponding translations
|
||||
|
||||
## Supported Template Formats
|
||||
|
||||
The implementation supports both syntax formats:
|
||||
- `{parameter}` - Required parameter
|
||||
- `{parameter, default=value}` - Parameter with default value (flexible spacing)
|
||||
- `{parameter,default=value}` - Parameter with default value (no spacing)
|
||||
|
||||
## Example Templates
|
||||
|
||||
✅ **Valid Templates:**
|
||||
- `https://google.com/search?q={query}`
|
||||
- `https://google.com/search?q={query, default=hello}`
|
||||
- `https://github.com/{user}/{repo}`
|
||||
- `https://maps.apple.com/?q={location,default=New York}`
|
||||
|
||||
⚠️ **Templates needing parameters:**
|
||||
- `https://example.com/no-parameters`
|
||||
|
||||
❌ **Invalid syntax examples:**
|
||||
- URLs with malformed parameter syntax
|
||||
- Non-URL strings when template type is selected
|
||||
|
||||
## User Experience
|
||||
|
||||
1. **Select Template type** in link form
|
||||
2. **See helpful tips** with syntax explanation and example
|
||||
3. **Enter URL with parameters** - real-time validation appears
|
||||
4. **Visual feedback**:
|
||||
- Blue border and checkmark for valid templates
|
||||
- Orange warning for templates without parameters
|
||||
- Red error for invalid syntax
|
||||
5. **Clear error messages** to guide correction
|
||||
|
||||
The implementation provides a smooth, intuitive experience for creating dynamic URL templates with immediate feedback.
|
||||
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env swift
|
||||
|
||||
import Foundation
|
||||
|
||||
// Copy the URLTemplateProcessor code for testing
|
||||
struct URLTemplateProcessor {
|
||||
static func processTemplate(_ template: String, with parameters: [String: String]) -> String {
|
||||
var processedURL = template
|
||||
let extractedParams = extractParameters(from: template)
|
||||
|
||||
for param in extractedParams {
|
||||
let placeholder = "{\(param.name)" + (param.defaultValue != nil ? ",default=\(param.defaultValue!)" : "") + "}"
|
||||
let value = parameters[param.name] ?? param.defaultValue ?? ""
|
||||
processedURL = processedURL.replacingOccurrences(of: placeholder, with: value)
|
||||
}
|
||||
|
||||
return processedURL
|
||||
}
|
||||
|
||||
static func extractParameters(from template: String) -> [TemplateParameter] {
|
||||
var parameters: [TemplateParameter] = []
|
||||
// Updated pattern to handle optional spaces around comma and default=
|
||||
let pattern = "\\{([^,}]+)(?:\\s*,\\s*default\\s*=\\s*([^}]+))?\\}"
|
||||
|
||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
|
||||
return parameters
|
||||
}
|
||||
|
||||
let matches = regex.matches(in: template, options: [], range: NSRange(location: 0, length: template.utf16.count))
|
||||
|
||||
for match in matches {
|
||||
let nameRange = match.range(at: 1)
|
||||
let defaultRange = match.range(at: 2)
|
||||
|
||||
if let nameSwiftRange = Range(nameRange, in: template) {
|
||||
let name = String(template[nameSwiftRange]).trimmingCharacters(in: .whitespaces)
|
||||
var defaultValue: String? = nil
|
||||
|
||||
if defaultRange.location != NSNotFound,
|
||||
let defaultSwiftRange = Range(defaultRange, in: template) {
|
||||
defaultValue = String(template[defaultSwiftRange]).trimmingCharacters(in: .whitespaces)
|
||||
}
|
||||
|
||||
parameters.append(TemplateParameter(name: name, defaultValue: defaultValue))
|
||||
}
|
||||
}
|
||||
|
||||
return parameters
|
||||
}
|
||||
|
||||
static func validateTemplate(_ template: String) -> Bool {
|
||||
let extractedParams = extractParameters(from: template)
|
||||
|
||||
// Check if all placeholders are valid
|
||||
let pattern = "\\{[^{}]*\\}"
|
||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
|
||||
return false
|
||||
}
|
||||
|
||||
let matches = regex.matches(in: template, options: [], range: NSRange(location: 0, length: template.utf16.count))
|
||||
|
||||
// Each match should correspond to a valid parameter
|
||||
for match in matches {
|
||||
if let matchRange = Range(match.range, in: template) {
|
||||
let matchString = String(template[matchRange])
|
||||
let isValid = extractedParams.contains { param in
|
||||
matchString.contains(param.name)
|
||||
}
|
||||
if !isValid {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
static func hasParameters(_ template: String) -> Bool {
|
||||
return template.contains("{") && template.contains("}")
|
||||
}
|
||||
}
|
||||
|
||||
struct TemplateParameter {
|
||||
let name: String
|
||||
let defaultValue: String?
|
||||
|
||||
var isRequired: Bool {
|
||||
return defaultValue == nil
|
||||
}
|
||||
}
|
||||
|
||||
// Test cases
|
||||
print("Testing URLTemplateProcessor...")
|
||||
|
||||
let testCases = [
|
||||
"https://google.com/search?q={query, default=hello}",
|
||||
"https://google.com/search?q={query}",
|
||||
"https://github.com/{user}/{repo}",
|
||||
"https://youtube.com/watch?v={videoId}",
|
||||
"https://maps.apple.com/?q={location,default=New York}",
|
||||
"https://example.com/{invalid syntax}",
|
||||
"https://example.com/no-parameters",
|
||||
"https://example.com/{query,default=test}/{page,default=1}"
|
||||
]
|
||||
|
||||
for testCase in testCases {
|
||||
let isValid = URLTemplateProcessor.validateTemplate(testCase)
|
||||
let hasParams = URLTemplateProcessor.hasParameters(testCase)
|
||||
let parameters = URLTemplateProcessor.extractParameters(from: testCase)
|
||||
|
||||
print("\nTest: \(testCase)")
|
||||
print(" Valid: \(isValid)")
|
||||
print(" Has Parameters: \(hasParams)")
|
||||
print(" Parameters: \(parameters.map { "\($0.name)" + (($0.defaultValue != nil) ? ",default=\($0.defaultValue!)" : "") })")
|
||||
|
||||
if isValid && hasParams {
|
||||
print(" ✅ Would show blue border")
|
||||
} else if !hasParams {
|
||||
print(" ⚠️ Would show warning (no parameters)")
|
||||
} else {
|
||||
print(" ❌ Would show error (invalid)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user