mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 17:29:08 +08:00
Fix default index.html serving.
Support multi-language default. Remove default page from production builds. Improve defaultindex.html.
This commit is contained in:
parent
beacf06c7d
commit
a0b2ab7c0a
@ -21,9 +21,6 @@ type Logger interface {
|
|||||||
Error(message string, args ...interface{})
|
Error(message string, args ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed defaultindex.html
|
|
||||||
var defaultHTML []byte
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
indexHTML = "index.html"
|
indexHTML = "index.html"
|
||||||
)
|
)
|
||||||
@ -120,7 +117,9 @@ func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, fi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer func() {
|
||||||
|
_ = file.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
statInfo, err := file.Stat()
|
statInfo, err := file.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -143,7 +142,9 @@ func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, fi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer func() {
|
||||||
|
_ = file.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
statInfo, err = file.Stat()
|
statInfo, err = file.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
0
v3/examples/dialogs-basic/.hidden_file
Normal file
0
v3/examples/dialogs-basic/.hidden_file
Normal file
36
v3/examples/dialogs-basic/README.md
Normal file
36
v3/examples/dialogs-basic/README.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Dialog Test Application
|
||||||
|
|
||||||
|
This application is designed to test macOS file dialog functionality across different versions of macOS. It provides a comprehensive suite of tests for various dialog features and configurations.
|
||||||
|
|
||||||
|
## Features Tested
|
||||||
|
|
||||||
|
1. Basic file open dialog
|
||||||
|
2. Single extension filter
|
||||||
|
3. Multiple extension filter
|
||||||
|
4. Multiple file selection
|
||||||
|
5. Directory selection
|
||||||
|
6. Save dialog with extension
|
||||||
|
7. Complex filters
|
||||||
|
8. Hidden files
|
||||||
|
9. Default directory
|
||||||
|
10. Full featured dialog with all options
|
||||||
|
|
||||||
|
## Running the Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Results
|
||||||
|
|
||||||
|
When running tests:
|
||||||
|
- Each test will show the selected file(s) and their types
|
||||||
|
- For multiple selections, all selected files will be listed
|
||||||
|
- Errors will be displayed in an error dialog
|
||||||
|
- The application logs debug information to help track issues
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- This test application is primarily for development and testing purposes
|
||||||
|
- It can be used to verify dialog behavior across different macOS versions
|
||||||
|
- The tests are designed to not interfere with CI pipelines
|
260
v3/examples/dialogs-basic/main.go
Normal file
260
v3/examples/dialogs-basic/main.go
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := application.New(application.Options{
|
||||||
|
Name: "Dialog Test",
|
||||||
|
Description: "Test application for macOS dialogs",
|
||||||
|
Logger: application.DefaultLogger(slog.LevelDebug),
|
||||||
|
Mac: application.MacOptions{
|
||||||
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create main window
|
||||||
|
mainWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||||
|
Title: "Dialog Tests",
|
||||||
|
Width: 800,
|
||||||
|
Height: 600,
|
||||||
|
MinWidth: 800,
|
||||||
|
MinHeight: 600,
|
||||||
|
})
|
||||||
|
mainWindow.SetAlwaysOnTop(true)
|
||||||
|
|
||||||
|
// Create main menu
|
||||||
|
menu := app.NewMenu()
|
||||||
|
app.SetMenu(menu)
|
||||||
|
menu.AddRole(application.AppMenu)
|
||||||
|
menu.AddRole(application.EditMenu)
|
||||||
|
menu.AddRole(application.WindowMenu)
|
||||||
|
|
||||||
|
// Add test menu
|
||||||
|
testMenu := menu.AddSubmenu("Tests")
|
||||||
|
|
||||||
|
// Test 1: Basic file open with no filters (no window)
|
||||||
|
testMenu.Add("1. Basic Open (No Window)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, err := application.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
showResult("Basic Open", result, err, nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 1b: Basic file open with window
|
||||||
|
testMenu.Add("1b. Basic Open (With Window)").OnClick(func(ctx *application.Context) {
|
||||||
|
result, err := application.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
AttachToWindow(mainWindow).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
showResult("Basic Open", result, err, mainWindow)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 2: Open with single extension filter
|
||||||
|
testMenu.Add("2. Single Filter").OnClick(func(ctx *application.Context) {
|
||||||
|
result, err := application.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
AddFilter("Text Files", "*.txt").
|
||||||
|
AttachToWindow(mainWindow).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
showResult("Single Filter", result, err, mainWindow)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 3: Open with multiple extension filter
|
||||||
|
testMenu.Add("3. Multiple Filter").OnClick(func(ctx *application.Context) {
|
||||||
|
result, err := application.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
AddFilter("Documents", "*.txt;*.md;*.doc;*.docx").
|
||||||
|
AttachToWindow(mainWindow).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
showResult("Multiple Filter", result, err, mainWindow)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 4: Multiple file selection
|
||||||
|
testMenu.Add("4. Multiple Selection").OnClick(func(ctx *application.Context) {
|
||||||
|
results, err := application.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
AddFilter("Images", "*.png;*.jpg;*.jpeg").
|
||||||
|
AttachToWindow(mainWindow).
|
||||||
|
PromptForMultipleSelection()
|
||||||
|
if err != nil {
|
||||||
|
showError("Multiple Selection", err, mainWindow)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
showResults("Multiple Selection", results, mainWindow)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 5: Directory selection
|
||||||
|
testMenu.Add("5. Directory Selection").OnClick(func(ctx *application.Context) {
|
||||||
|
result, err := application.OpenFileDialog().
|
||||||
|
CanChooseDirectories(true).
|
||||||
|
CanChooseFiles(false).
|
||||||
|
AttachToWindow(mainWindow).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
showResult("Directory Selection", result, err, mainWindow)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 6: Save dialog with extension
|
||||||
|
testMenu.Add("6. Save Dialog").OnClick(func(ctx *application.Context) {
|
||||||
|
result, err := application.SaveFileDialog().
|
||||||
|
SetFilename("test.txt").
|
||||||
|
AddFilter("Text Files", "*.txt").
|
||||||
|
AttachToWindow(mainWindow).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
showResult("Save Dialog", result, err, mainWindow)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 7: Complex filters
|
||||||
|
testMenu.Add("7. Complex Filters").OnClick(func(ctx *application.Context) {
|
||||||
|
result, err := application.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
AddFilter("All Documents", "*.txt;*.md;*.doc;*.docx;*.pdf").
|
||||||
|
AddFilter("Text Files", "*.txt").
|
||||||
|
AddFilter("Markdown", "*.md").
|
||||||
|
AddFilter("Word Documents", "*.doc;*.docx").
|
||||||
|
AddFilter("PDF Files", "*.pdf").
|
||||||
|
AttachToWindow(mainWindow).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
showResult("Complex Filters", result, err, mainWindow)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 8: Hidden files
|
||||||
|
testMenu.Add("8. Show Hidden").OnClick(func(ctx *application.Context) {
|
||||||
|
result, err := application.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
ShowHiddenFiles(true).
|
||||||
|
AttachToWindow(mainWindow).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
showResult("Show Hidden", result, err, mainWindow)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 9: Default directory
|
||||||
|
testMenu.Add("9. Default Directory").OnClick(func(ctx *application.Context) {
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
|
result, err := application.OpenFileDialog().
|
||||||
|
CanChooseFiles(true).
|
||||||
|
SetDirectory(home).
|
||||||
|
AttachToWindow(mainWindow).
|
||||||
|
PromptForSingleSelection()
|
||||||
|
showResult("Default Directory", result, err, mainWindow)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 10: Full featured dialog
|
||||||
|
testMenu.Add("10. Full Featured").OnClick(func(ctx *application.Context) {
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
|
dialog := application.OpenFileDialog().
|
||||||
|
SetTitle("Full Featured Dialog").
|
||||||
|
SetDirectory(home).
|
||||||
|
CanChooseFiles(true).
|
||||||
|
CanCreateDirectories(true).
|
||||||
|
ShowHiddenFiles(true).
|
||||||
|
ResolvesAliases(true).
|
||||||
|
AllowsOtherFileTypes(true).
|
||||||
|
AttachToWindow(mainWindow)
|
||||||
|
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
dialog.SetMessage("Please select files")
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.AddFilter("All Supported", "*.txt;*.md;*.pdf;*.png;*.jpg")
|
||||||
|
dialog.AddFilter("Documents", "*.txt;*.md;*.pdf")
|
||||||
|
dialog.AddFilter("Images", "*.png;*.jpg;*.jpeg")
|
||||||
|
|
||||||
|
results, err := dialog.PromptForMultipleSelection()
|
||||||
|
if err != nil {
|
||||||
|
showError("Full Featured", err, mainWindow)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
showResults("Full Featured", results, mainWindow)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Show the window
|
||||||
|
mainWindow.Show()
|
||||||
|
|
||||||
|
// Run the app
|
||||||
|
if err := app.Run(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showResult(test string, result string, err error, window *application.WebviewWindow) {
|
||||||
|
if err != nil {
|
||||||
|
showError(test, err, window)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result == "" {
|
||||||
|
dialog := application.InfoDialog().
|
||||||
|
SetTitle(test).
|
||||||
|
SetMessage("No file selected")
|
||||||
|
if window != nil {
|
||||||
|
dialog.AttachToWindow(window)
|
||||||
|
}
|
||||||
|
dialog.Show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dialog := application.InfoDialog().
|
||||||
|
SetTitle(test).
|
||||||
|
SetMessage(fmt.Sprintf("Selected: %s\nType: %s", result, getFileType(result)))
|
||||||
|
if window != nil {
|
||||||
|
dialog.AttachToWindow(window)
|
||||||
|
}
|
||||||
|
dialog.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func showResults(test string, results []string, window *application.WebviewWindow) {
|
||||||
|
if len(results) == 0 {
|
||||||
|
dialog := application.InfoDialog().
|
||||||
|
SetTitle(test).
|
||||||
|
SetMessage("No files selected")
|
||||||
|
if window != nil {
|
||||||
|
dialog.AttachToWindow(window)
|
||||||
|
}
|
||||||
|
dialog.Show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var message strings.Builder
|
||||||
|
message.WriteString(fmt.Sprintf("Selected %d files:\n\n", len(results)))
|
||||||
|
for _, result := range results {
|
||||||
|
message.WriteString(fmt.Sprintf("%s (%s)\n", result, getFileType(result)))
|
||||||
|
}
|
||||||
|
dialog := application.InfoDialog().
|
||||||
|
SetTitle(test).
|
||||||
|
SetMessage(message.String())
|
||||||
|
if window != nil {
|
||||||
|
dialog.AttachToWindow(window)
|
||||||
|
}
|
||||||
|
dialog.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func showError(test string, err error, window *application.WebviewWindow) {
|
||||||
|
dialog := application.ErrorDialog().
|
||||||
|
SetTitle(test).
|
||||||
|
SetMessage(fmt.Sprintf("Error: %v", err))
|
||||||
|
if window != nil {
|
||||||
|
dialog.AttachToWindow(window)
|
||||||
|
}
|
||||||
|
dialog.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileType(path string) string {
|
||||||
|
if path == "" {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
ext := strings.ToLower(filepath.Ext(path))
|
||||||
|
if ext == "" {
|
||||||
|
if fi, err := os.Stat(path); err == nil && fi.IsDir() {
|
||||||
|
return "directory"
|
||||||
|
}
|
||||||
|
return "no extension"
|
||||||
|
}
|
||||||
|
return ext
|
||||||
|
}
|
1
v3/examples/dialogs-basic/test.txt
Normal file
1
v3/examples/dialogs-basic/test.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
This is a sample text file to test filtering.
|
BIN
v3/examples/dialogs-basic/wails-logo-small.jpg
Normal file
BIN
v3/examples/dialogs-basic/wails-logo-small.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
v3/examples/dialogs-basic/wails-logo-small.png
Normal file
BIN
v3/examples/dialogs-basic/wails-logo-small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
@ -14,6 +14,7 @@ const (
|
|||||||
webViewRequestHeaderWindowId = "x-wails-window-id"
|
webViewRequestHeaderWindowId = "x-wails-window-id"
|
||||||
webViewRequestHeaderWindowName = "x-wails-window-name"
|
webViewRequestHeaderWindowName = "x-wails-window-name"
|
||||||
servicePrefix = "wails/services"
|
servicePrefix = "wails/services"
|
||||||
|
HeaderAcceptLanguage = "accept-language"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RuntimeHandler interface {
|
type RuntimeHandler interface {
|
||||||
@ -100,7 +101,14 @@ func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userH
|
|||||||
a.writeBlob(rw, indexHTML, recorder.Body.Bytes())
|
a.writeBlob(rw, indexHTML, recorder.Body.Bytes())
|
||||||
|
|
||||||
case http.StatusNotFound:
|
case http.StatusNotFound:
|
||||||
a.writeBlob(rw, indexHTML, defaultIndexHTML())
|
// Read the accept-language header
|
||||||
|
acceptLanguage := req.Header.Get(HeaderAcceptLanguage)
|
||||||
|
if acceptLanguage == "" {
|
||||||
|
acceptLanguage = "en"
|
||||||
|
}
|
||||||
|
// Set content type for default index.html
|
||||||
|
header.Set(HeaderContentType, "text/html; charset=utf-8")
|
||||||
|
a.writeBlob(rw, indexHTML, defaultIndexHTML(acceptLanguage))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
rw.WriteHeader(recorder.Code)
|
rw.WriteHeader(recorder.Code)
|
||||||
|
@ -2,6 +2,43 @@
|
|||||||
|
|
||||||
package assetserver
|
package assetserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
_ "embed"
|
||||||
|
"io"
|
||||||
|
iofs "io/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed defaults
|
||||||
|
var defaultHTML embed.FS
|
||||||
|
|
||||||
|
func defaultIndexHTML(language string) []byte {
|
||||||
|
result := []byte("index.html not found")
|
||||||
|
// Create an fs.Sub in the defaults directory
|
||||||
|
defaults, err := iofs.Sub(defaultHTML, "defaults")
|
||||||
|
if err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
// Get the 2 character language code
|
||||||
|
lang := "en"
|
||||||
|
if len(language) >= 2 {
|
||||||
|
lang = language[:2]
|
||||||
|
}
|
||||||
|
// Now we can read the index.html file in the format
|
||||||
|
// index.<lang>.html.
|
||||||
|
|
||||||
|
indexFile, err := defaults.Open("index." + lang + ".html")
|
||||||
|
if err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
indexBytes, err := io.ReadAll(indexFile)
|
||||||
|
if err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return indexBytes
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AssetServer) LogDetails() {
|
func (a *AssetServer) LogDetails() {
|
||||||
var info = []any{
|
var info = []any{
|
||||||
"middleware", a.options.Middleware != nil,
|
"middleware", a.options.Middleware != nil,
|
||||||
|
@ -2,4 +2,8 @@
|
|||||||
|
|
||||||
package assetserver
|
package assetserver
|
||||||
|
|
||||||
|
func defaultIndexHTML(_ string) []byte {
|
||||||
|
return []byte("index.html not found")
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AssetServer) LogDetails() {}
|
func (a *AssetServer) LogDetails() {}
|
||||||
|
@ -11,13 +11,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed defaultindex.html
|
|
||||||
var defaultHTML []byte
|
|
||||||
|
|
||||||
func defaultIndexHTML() []byte {
|
|
||||||
return defaultHTML
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAssetFileServer(vfs fs.FS) http.Handler {
|
func NewAssetFileServer(vfs fs.FS) http.Handler {
|
||||||
devServerURL := GetDevServerURL()
|
devServerURL := GetDevServerURL()
|
||||||
if devServerURL == "" {
|
if devServerURL == "" {
|
||||||
|
File diff suppressed because one or more lines are too long
350
v3/internal/assetserver/defaults/index.en.html
Normal file
350
v3/internal/assetserver/defaults/index.en.html
Normal file
File diff suppressed because one or more lines are too long
302
v3/internal/assetserver/defaults/index.zh.html
Normal file
302
v3/internal/assetserver/defaults/index.zh.html
Normal file
File diff suppressed because one or more lines are too long
@ -3,10 +3,14 @@
|
|||||||
#ifndef _DIALOGS_DELEGATE_H_
|
#ifndef _DIALOGS_DELEGATE_H_
|
||||||
#define _DIALOGS_DELEGATE_H_
|
#define _DIALOGS_DELEGATE_H_
|
||||||
|
|
||||||
#import <UniformTypeIdentifiers/UTType.h>
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
// create an NSOpenPanel delegate to handle the callback
|
// Conditionally import UniformTypeIdentifiers based on OS version
|
||||||
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
|
||||||
|
#import <UniformTypeIdentifiers/UTType.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// OpenPanel delegate to handle file filtering
|
||||||
@interface OpenPanelDelegate : NSObject <NSOpenSavePanelDelegate>
|
@interface OpenPanelDelegate : NSObject <NSOpenSavePanelDelegate>
|
||||||
@property (nonatomic, strong) NSArray *allowedExtensions;
|
@property (nonatomic, strong) NSArray *allowedExtensions;
|
||||||
@end
|
@end
|
||||||
|
@ -8,29 +8,31 @@
|
|||||||
if (url == nil) {
|
if (url == nil) {
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||||
BOOL isDirectory = NO;
|
BOOL isDirectory = NO;
|
||||||
if ([fileManager fileExistsAtPath:url.path isDirectory:&isDirectory] && isDirectory) {
|
if ([fileManager fileExistsAtPath:url.path isDirectory:&isDirectory] && isDirectory) {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
if (self.allowedExtensions == nil) {
|
|
||||||
|
// If no extensions specified, allow all files
|
||||||
|
if (self.allowedExtensions == nil || [self.allowedExtensions count] == 0) {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
NSString *extension = url.pathExtension;
|
|
||||||
if (extension == nil) {
|
NSString *extension = [url.pathExtension lowercaseString];
|
||||||
|
if (extension == nil || [extension isEqualToString:@""]) {
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
if ([extension isEqualToString:@""]) {
|
|
||||||
return NO;
|
// Check if the extension is in our allowed list (case insensitive)
|
||||||
}
|
for (NSString *allowedExt in self.allowedExtensions) {
|
||||||
if ([self.allowedExtensions containsObject:extension]) {
|
if ([[allowedExt lowercaseString] isEqualToString:extension]) {
|
||||||
return YES;
|
return YES;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user