Revision: 29442
http://sourceforge.net/p/bibdesk/svn/29442
Author: hofman
Date: 2025-08-23 16:19:22 +0000 (Sat, 23 Aug 2025)
Log Message:
-----------
Replace text import weview by WKWebView subclass. Use user scripts and message
handler to handle selection change and link hover. Use subclass to allow
customizing the context menu, use a trick shadowing a Open Link in New Window
menu item to allow custom menu items using the link, as this is passed by the
UI delegate methlod this calls. Allow adding WKDownload objects to download
manager.
Modified Paths:
--------------
trunk/bibdesk/BDSKDownloadManager.h
trunk/bibdesk/BDSKDownloadManager.m
trunk/bibdesk/BDSKTextImportController.h
trunk/bibdesk/BDSKTextImportController.m
trunk/bibdesk/de.lproj/Localizable.strings
trunk/bibdesk/en.lproj/Localizable.strings
trunk/bibdesk/fr.lproj/Localizable.strings
Modified: trunk/bibdesk/BDSKDownloadManager.h
===================================================================
--- trunk/bibdesk/BDSKDownloadManager.h 2025-08-21 16:56:15 UTC (rev 29441)
+++ trunk/bibdesk/BDSKDownloadManager.h 2025-08-23 16:19:22 UTC (rev 29442)
@@ -62,6 +62,8 @@
- (void)cancel:(NSUInteger)uniqueID;
- (void)remove:(NSUInteger)uniqueID;
+- (void)addDownload:(id)download;
+
@end
#pragma mark -
@@ -71,12 +73,12 @@
NSURL *URL;
NSURL *fileURL;
BDSKDownloadStatus status;
- NSURLDownload *URLDownload;
+ id download;
}
-- (instancetype)initWithURLDownload:(NSURLDownload *)aDownload;
+- (instancetype)initWithDownload:(id)aDownload URL:(NSURL *)aURL;
-@property (nonatomic, readonly) NSURLDownload *URLDownload;
+@property (nonatomic, readonly) id download;
@property (nonatomic, readonly) NSUInteger uniqueID;
@property (nonatomic, readonly) NSURL *URL;
@property (nonatomic, nullable, strong) NSURL *fileURL;
@@ -83,6 +85,8 @@
@property (nonatomic, nullable, readonly) NSString *fileName;
@property (nonatomic) BDSKDownloadStatus status;
+- (void)cancel;
+
@end
NS_ASSUME_NONNULL_END
Modified: trunk/bibdesk/BDSKDownloadManager.m
===================================================================
--- trunk/bibdesk/BDSKDownloadManager.m 2025-08-21 16:56:15 UTC (rev 29441)
+++ trunk/bibdesk/BDSKDownloadManager.m 2025-08-23 16:19:22 UTC (rev 29442)
@@ -40,10 +40,25 @@
#import "BDSKStringConstants.h"
#import "NSURL_BDSKExtensions.h"
#import "NSFileManager_BDSKExtensions.h"
+#import <WebKit/WebKit.h>
#define BDSKRemoveFinishedDownloadsKey @"BDSKRemoveFinishedDownloads"
#define BDSKRemoveFailedDownloadsKey @"BDSKRemoveFailedDownloads"
+#if MAC_OS_X_VERSION_MAX_ALLOWED < 110300
+@protocol WKDownloadDelegate <NSObject>
+@end
+@interface WKDownload : NSObject <NSProgressReporting>
+@property (nonatomic, readonly, nullable) NSURLRequest *originalRequest;
+@property (nonatomic, readonly, weak) WKWebView *webView;
+@property (nonatomic, weak) id <WKDownloadDelegate> delegate;
+- (void)cancel:(void(^)(NSData * resumeData))completionHandler;
+@end
+#endif
+
+@interface BDSKDownloadManager () <WKDownloadDelegate>
+@end
+
@implementation BDSKDownloadManager
@synthesize downloads;
@@ -80,9 +95,14 @@
[[NSUserDefaults standardUserDefaults] setBool:flag
forKey:BDSKRemoveFailedDownloadsKey];
}
-- (BDSKWebDownload *)downloadForURLDownload:(NSURLDownload *)URLDownload {
- for (BDSKWebDownload *download in downloads) {
- if ([download URLDownload] == URLDownload)
+- (void)addDownload:(id)download {
+ [download setDelegate:self];
+ [downloads addObject:[[BDSKWebDownload alloc] initWithDownload:download
URL:[[download originalRequest] URL]]];
+}
+
+- (BDSKWebDownload *)webDownloadForDownload:(id)download {
+ for (BDSKWebDownload *webDownload in downloads) {
+ if ([webDownload download] == download)
return download;
}
return nil;
@@ -89,9 +109,9 @@
}
- (BDSKWebDownload *)downloadWithUniqueID:(NSUInteger)uniqueID {
- for (BDSKWebDownload *download in downloads) {
- if ([download uniqueID] == uniqueID)
- return download;
+ for (BDSKWebDownload *webDownload in downloads) {
+ if ([webDownload uniqueID] == uniqueID)
+ return webDownload;
}
return nil;
}
@@ -105,13 +125,13 @@
}
- (void)cancel:(NSUInteger)uniqueID {
- [[[self downloadWithUniqueID:uniqueID] URLDownload] cancel];
+ [[self downloadWithUniqueID:uniqueID] cancel];
}
- (void)remove:(NSUInteger)uniqueID {
- BDSKWebDownload *download = [self downloadWithUniqueID:uniqueID];
- if (download)
- [downloads removeObject:download];
+ BDSKWebDownload *webDownload = [self downloadWithUniqueID:uniqueID];
+ if (webDownload)
+ [downloads removeObject:webDownload];
}
+ (NSString *)webScriptNameForSelector:(SEL)aSelector {
@@ -140,24 +160,24 @@
#pragma mark NSURLDownload delegate protocol
-- (void)downloadDidBegin:(NSURLDownload *)URLDownload {
- [downloads addObject:[[BDSKWebDownload alloc]
initWithURLDownload:URLDownload]];
+- (void)downloadDidBegin:(NSURLDownload *)download {
+ [downloads addObject:[[BDSKWebDownload alloc] initWithDownload:download
URL:[[download request] URL]]];
}
-- (void)downloadDidFinish:(NSURLDownload *)URLDownload {
- BDSKWebDownload *download = [self downloadForURLDownload:URLDownload];
- [download setStatus:BDSKDownloadStatusFinished];
+- (void)downloadDidFinish:(id)download {
+ BDSKWebDownload *webDownload = [self webDownloadForDownload:download];
+ [webDownload setStatus:BDSKDownloadStatusFinished];
- if ([[NSUserDefaults standardUserDefaults]
boolForKey:BDSKRemoveFinishedDownloadsKey] && download)
- [downloads removeObject:download];
+ if ([[NSUserDefaults standardUserDefaults]
boolForKey:BDSKRemoveFinishedDownloadsKey] && webDownload)
+ [downloads removeObject:webDownload];
}
-- (void)download:(NSURLDownload *)URLDownload didFailWithError:(NSError
*)error {
- BDSKWebDownload *download = [self downloadForURLDownload:URLDownload];
- [download setStatus:BDSKDownloadStatusFinished];
+- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error {
+ BDSKWebDownload *webDownload = [self webDownloadForDownload:download];
+ [webDownload setStatus:BDSKDownloadStatusFinished];
- if ([[NSUserDefaults standardUserDefaults]
boolForKey:BDSKRemoveFailedDownloadsKey] && download)
- [downloads removeObject:download];
+ if ([[NSUserDefaults standardUserDefaults]
boolForKey:BDSKRemoveFailedDownloadsKey] && webDownload)
+ [downloads removeObject:webDownload];
NSString *errorDescription = [error localizedDescription] ?:
NSLocalizedString(@"An error occured during download.", @"Informative text in
alert dialog");
NSAlert *alert = [[NSAlert alloc] init];
@@ -166,7 +186,7 @@
[alert runModal];
}
-- (void)download:(NSURLDownload *)URLDownload
decideDestinationWithSuggestedFilename:(NSString *)filename {
+- (void)download:(NSURLDownload *)download
decideDestinationWithSuggestedFilename:(NSString *)filename {
NSString *extension = [filename pathExtension];
NSString *downloadsDirectory = [[[NSUserDefaults standardUserDefaults]
stringForKey:BDSKDownloadsDirectoryKey] stringByExpandingTildeInPath];
@@ -187,19 +207,69 @@
// we need to do this modally, not using a sheet, as the download may
otherwise finish on Leopard before the sheet is done
NSInteger returnCode = [sPanel runModal];
if (returnCode == NSModalResponseOK)
- [URLDownload setDestination:[[sPanel URL] path] allowOverwrite:YES];
+ [download setDestination:[[sPanel URL] path] allowOverwrite:YES];
else
- [URLDownload cancel];
+ [download cancel];
}
-- (void)download:(NSURLDownload *)URLDownload didCreateDestination:(NSString
*)path {
- [[self downloadForURLDownload:URLDownload] setFileURL:[NSURL
fileURLWithPath:path isDirectory:NO]];
+- (void)download:(NSURLDownload *)download didCreateDestination:(NSString
*)path {
+ [[self webDownloadForDownload:download] setFileURL:[NSURL
fileURLWithPath:path isDirectory:NO]];
}
-- (BOOL)download:(NSURLDownload *)URLDownload
shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType {
+- (BOOL)download:(NSURLDownload *)download
shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType {
return YES;
}
+#pragma mark WKDownload delegate protocol
+
+- (void)download:(WKDownload *)download didFailWithError:(NSError *)error
resumeData:(NSData *)resumeData API_AVAILABLE(macos(11.3)) {
+ BDSKWebDownload *webDownload = [self webDownloadForDownload:download];
+ [webDownload setStatus:BDSKDownloadStatusFinished];
+
+ if ([[NSUserDefaults standardUserDefaults]
boolForKey:BDSKRemoveFailedDownloadsKey] && webDownload)
+ [downloads removeObject:webDownload];
+
+ NSString *errorDescription = [error localizedDescription] ?:
NSLocalizedString(@"An error occured during download.", @"Informative text in
alert dialog");
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:NSLocalizedString(@"Download Failed", @"Message in
alert dialog when download failed")];
+ [alert setInformativeText:errorDescription];
+ [alert runModal];
+}
+
+- (void)download:(WKDownload *)download
decideDestinationUsingResponse:(NSURLResponse *)response
suggestedFilename:(NSString *)filename completionHandler:(void (^)(NSURL *
destination))completionHandler API_AVAILABLE(macos(11.3)) {
+ NSString *extension = [filename pathExtension];
+
+ NSString *downloadsDirectory = [[[NSUserDefaults standardUserDefaults]
stringForKey:BDSKDownloadsDirectoryKey] stringByExpandingTildeInPath];
+ NSURL *downloadsURL = nil;
+ if (downloadsDirectory)
+ downloadsURL = [NSURL fileURLWithPath:downloadsDirectory
isDirectory:YES];
+ else
+ downloadsURL = [[NSFileManager defaultManager] downloadFolderURL];
+
+ NSSavePanel *sPanel = [NSSavePanel savePanel];
+ if (NO == [extension isEqualToString:@""])
+ [sPanel setAllowedFileTypes:@[extension]];
+ [sPanel setAllowsOtherFileTypes:YES];
+ [sPanel setCanSelectHiddenExtension:YES];
+ [sPanel setNameFieldStringValue:filename];
+ [sPanel setDirectoryURL:downloadsURL];
+
+ NSWindow *window = [[download webView] window];
+ void (^handler)(NSModalResponse) = ^(NSModalResponse result){
+ if (result == NSModalResponseOK) {
+ NSURL *fileURL = [[NSFileManager defaultManager]
uniqueFileURL:[sPanel URL]];
+ completionHandler(fileURL);
+ [[self webDownloadForDownload:download] setFileURL:fileURL];
+ } else {
+ completionHandler(nil);
+ }
+ };
+ if (window)
+ [sPanel beginSheetModalForWindow:window completionHandler:handler];
+ else
+ [sPanel beginWithCompletionHandler:handler];
+}
+
@end
#pragma mark -
@@ -206,19 +276,19 @@
@implementation BDSKWebDownload
-@synthesize URLDownload, uniqueID, URL, fileURL, status;
+@synthesize download, uniqueID, URL, fileURL, status;
@dynamic fileName;
static NSUInteger currentUniqueID = 0;
-- (instancetype)initWithURLDownload:(NSURLDownload *)aURLDownload {
+- (instancetype)initWithDownload:(id)aDownload URL:(NSURL *)aURL {
self = [super init];
if (self) {
uniqueID = ++currentUniqueID;
- URL = [[aURLDownload request] URL];
+ URL = aURL;
fileURL = nil;
status = BDSKDownloadStatusDownloading;
- URLDownload = aURLDownload;
+ download = aDownload;
}
return self;
}
@@ -241,10 +311,17 @@
if (status != newStatus) {
status = newStatus;
if (status != BDSKDownloadStatusDownloading)
- URLDownload = nil;
+ download = nil;
if (status == BDSKDownloadStatusFailed)
[self setFileURL:nil];
}
}
+- (void)cancel {
+ if ([download isKindOfClass:[NSURLDownload class]])
+ [download cancel];
+ else if (@available(macOS 11.3, *))
+ [(WKDownload *)download cancel:nil];
+}
+
@end
Modified: trunk/bibdesk/BDSKTextImportController.h
===================================================================
--- trunk/bibdesk/BDSKTextImportController.h 2025-08-21 16:56:15 UTC (rev
29441)
+++ trunk/bibdesk/BDSKTextImportController.h 2025-08-23 16:19:22 UTC (rev
29442)
@@ -40,15 +40,14 @@
#import "BDSKOwnerProtocol.h"
#import "BDSKTextImportItemTableView.h"
#import "BDSKCitationFormatter.h"
-#import "BDSKWebView.h"
#import "BDSKDownloader.h"
NS_ASSUME_NONNULL_BEGIN
-@class BibDocument, BibItem, BDSKEdgeView, WebDownload;
+@class BibDocument, BibItem, BDSKEdgeView, BDSKWKWebView;
@class BDSKCiteKeyFormatter, BDSKTextImportItemTableView,
BDSKComplexStringFormatter;
-@interface BDSKTextImportController : NSWindowController <BDSKOwner,
BDSKTextImportItemTableViewDelegate, NSTableViewDataSource, NSTextViewDelegate,
NSSplitViewDelegate, BDSKCitationFormatterDelegate, BDSKWebViewDelegate,
BDSKDownloadDelegate, NSTouchBarDelegate> {
+@interface BDSKTextImportController : NSWindowController <BDSKOwner,
BDSKTextImportItemTableViewDelegate, NSTableViewDataSource, NSTextViewDelegate,
NSSplitViewDelegate, BDSKCitationFormatterDelegate, BDSKDownloadDelegate,
NSTouchBarDelegate> {
NSTextView *sourceTextView;
BDSKTextImportItemTableView *itemTableView;
NSTextField *citeKeyField;
@@ -87,7 +86,8 @@
BOOL isLoading;
BOOL isDownloading;
- BDSKWebView *webView;
+ BDSKWKWebView *webView;
+ NSInteger customMenuAction;
BDSKDownload *download;
NSString *downloadFileName;
@@ -146,7 +146,6 @@
- (void)copyLocationAsRemoteUrl:(nullable id)sender;
- (void)copyLinkedLocationAsRemoteUrl:(nullable id)sender;
-- (void)saveFileAsLocalUrl:(nullable id)sender;
- (void)downloadLinkedFileAsLocalUrl:(nullable id)sender;
@end
Modified: trunk/bibdesk/BDSKTextImportController.m
===================================================================
--- trunk/bibdesk/BDSKTextImportController.m 2025-08-21 16:56:15 UTC (rev
29441)
+++ trunk/bibdesk/BDSKTextImportController.m 2025-08-23 16:19:22 UTC (rev
29442)
@@ -70,6 +70,8 @@
#import "NSTableView_BDSKExtensions.h"
#import "NSString_BDSKExtensions.h"
#import "BDSKComplexString.h"
+#import "NSWorkspace_BDSKExtensions.h"
+#import "BDSKDownloadManager.h"
#define BDSKTextImportControllerFrameAutosaveName @"BDSKTextImportController
Frame Autosave Name"
@@ -81,11 +83,48 @@
#define FIELDNAME_COLUMN @"FieldName"
#define KEY_COLUMN @"Num"
-@interface BDSKTextImportController (Private)
+enum {
+ BDSKWebViewCustomMenuActionNone,
+ BDSKWebViewCustomMenuActionOpenLink,
+ BDSKWebViewCustomMenuActionBookmarkLink,
+ BDSKWebViewCustomMenuActionAddLink,
+ BDSKWebViewCustomMenuActionDownloadLink
+};
-- (void)handleWebViewDidChangeSelection:(NSNotification *)notification;
+#if MAC_OS_X_VERSION_MAX_ALLOWED < 110300
+@class WKDownload;
+#define WKNavigationActionPolicyDownload 2
+#define WKNavigationResponsePolicyDownload 2
+#endif
+
+@protocol BDSKUIDelegate <WKUIDelegate>
+@optional
+- (void)webView:(WKWebView *)webView willPopUpMenu:(NSMenu *)menu;
+@end
+
+@interface BDSKWKWebView : WKWebView
+@property (nullable, nonatomic, weak) id<BDSKUIDelegate> UIDelegate;
+@end
+
+#pragma mark -
+
+@protocol BDSKScriptMessageHandlerDelegate <NSObject>
+- (void)scriptMessageHandlerWithName:(NSString *)name
didReceiveMessage:(NSString *)body;
+@end
+
+@interface BDSKScriptMessageHandler : NSObject <WKScriptMessageHandler> {
+ __weak id<BDSKScriptMessageHandlerDelegate> delegate;
+}
+@property (nonatomic, nullable, weak) id<BDSKScriptMessageHandlerDelegate>
delegate;
+@end
+
+#pragma mark -
+
+@interface BDSKTextImportController () <WKNavigationDelegate, BDSKUIDelegate,
BDSKScriptMessageHandlerDelegate>
+
- (void)handleFlagsChangedNotification:(NSNotification *)notification;
- (void)handleBibItemChangedNotification:(NSNotification *)notification;
+- (void)handleTextSelectionChangedNotification:(NSNotification *)notification;
- (void)finalizeChangesPreservingSelection:(BOOL)shouldPreserveSelection;
@@ -93,7 +132,8 @@
- (void)showWebViewWithURL:(NSURL *)url;
- (void)loadFromFileURL:(NSURL *)url;
- (void)setShowingWebView:(BOOL)showWebView;
-- (void)setupTypeUI;
+- (void)setupWebView;
+- (void)clearWebView;
- (void)updateTypeAndFields;
- (void)updateColumnWidths;
@@ -106,7 +146,6 @@
- (BOOL)addCurrentSelectionToFieldAtIndex:(NSUInteger)index;
- (void)recordChangingField:(NSString *)fieldName toValue:(NSString *)value;
-- (void)autoDiscoverDataFromFrame:(WebFrame *)frame;
- (void)autoDiscoverDataFromString:(NSString *)string;
- (void)setCiteKeyDuplicateWarning:(BOOL)set;
@@ -126,8 +165,7 @@
[item setOwner:self];
[publications addObject:item];
fields = [[NSMutableArray alloc] init];
- webView = [[BDSKWebView alloc] init];
- [webView setDelegate:self];
+ webView = nil;
showingWebView = NO;
itemsAdded = [[NSMutableArray alloc] init];
webSelection = nil;
@@ -141,8 +179,8 @@
- (void)dealloc{
BDSKASSERT(download == nil);
- // next line is a workaround for a nasty webview crasher; looks like it
messages a garbage pointer to its undo manager
- [webView setDelegate:nil];
+ [webView setNavigationDelegate:nil];
+ [webView setUIDelegate:nil];
[itemTableView setDelegate:nil];
[itemTableView setDataSource:nil];
[splitView setDelegate:nil];
@@ -156,13 +194,10 @@
[statusLine setStringValue:@""];
- [webViewBox setEdges:BDSKEveryEdgeMask];
- [webViewBox setBackgroundColors:nil];
- [webViewBox setBackgroundView:nil];
- [webViewBox setContentView:webView];
+ [itemTypeButton removeAllItems];
+ [itemTypeButton addItemsWithTitles:[[BDSKTypeManager sharedManager]
types]];
+ [self updateTypeAndFields];
- [self setupTypeUI];
-
[sourceTextView setTextColor:[NSColor textColor]];
if (@available(macOS 10.14, *))
[sourceTextView setDrawsBackground:NO];
@@ -185,9 +220,9 @@
name:BDSKBibItemChangedNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
-
selector:@selector(handleWebViewDidChangeSelection:)
-
name:WebViewDidChangeSelectionNotification
- object:webView];
+
selector:@selector(handleTextSelectionChangedNotification:)
+
name:NSTextViewDidChangeSelectionNotification
+ object:sourceTextView];
}
#pragma mark Accessors
@@ -201,14 +236,12 @@
- (void)beginSheetForURL:(NSURL *)aURL modalForWindow:(NSWindow *)aWindow
completionHandler:(void (^)(NSInteger result))handler {
// make sure we loaded the nib
[self window];
- if (aURL == nil) {
+ if (aURL == nil)
[self loadPasteboardData];
- } else if ([aURL isFileURL]) {
+ else if ([aURL isFileURL])
[self loadFromFileURL:aURL];
- } else {
- [self setShowingWebView:YES];
- [webView setURL:aURL];
- }
+ else
+ [self showWebViewWithURL:aURL];
[super beginSheetModalForWindow:aWindow completionHandler:handler];
}
@@ -248,7 +281,7 @@
// cleanup
[self cancelDownload];
- [webView setDelegate:nil];
+ [self clearWebView];
[itemTableView setTypeSelectHelper:nil];
@@ -321,7 +354,7 @@
[alert setInformativeText:NSLocalizedString(@"Mac OS X does
not recognize this as a valid URL. Please re-enter the address and try
again.", @"Informative text in alert dialog")];
[alert beginSheetModalForWindow:[self window]
completionHandler:NULL];
} else {
- [webView setURL:url];
+ [webView loadRequest:[NSURLRequest requestWithURL:url]];
}
}
}];
@@ -341,8 +374,8 @@
- (IBAction)openBookmark:(id)sender{
NSURL *url = [sender representedObject];
- [self setShowingWebView:YES];
- [webView setURL:url];
+ if (url)
+ [self showWebViewWithURL:url];
}
- (IBAction)stopOrReloadAction:(id)sender{
@@ -432,8 +465,15 @@
#pragma mark WebView contextual menu actions
+- (void)addBookmark:(id)sender {
+ NSURL *aURL = [webView URL];
+ NSString *name = [webView title] ?: [aURL lastPathComponent];
+ if (aURL)
+ [[BDSKBookmarkController sharedBookmarkController]
addBookmarkWithURL:aURL proposedName:name modalForWindow:[self window]];
+}
+
- (void)copyLocationAsRemoteUrl:(id)sender{
- NSURL *aURL = [webView URL];
+ NSURL *aURL = [webView URL];
if (aURL) {
if ([[NSUserDefaults standardUserDefaults]
boolForKey:BDSKUseLocalUrlAndUrlKey])
@@ -456,38 +496,6 @@
}
}
-- (void)saveFileAsLocalUrl:(id)sender{
- WebDataSource *dataSource = [[webView mainFrame] dataSource];
- if (!dataSource || [dataSource isLoading])
- return;
-
- NSString *fileName = [[[[dataSource request] URL] relativePath]
lastPathComponent];
- NSString *extension = [fileName pathExtension];
-
- NSSavePanel *sPanel = [NSSavePanel savePanel];
- if (![extension isEqualToString:@""])
- [sPanel setAllowedFileTypes:[NSArray
arrayWithObjects:extension, nil]];
- [sPanel setCanCreateDirectories:YES];
- [sPanel setNameFieldStringValue:fileName];
-
- [sPanel beginSheetModalForWindow:[self window]
completionHandler:^(NSInteger result){
- if (result == NSModalResponseOK) {
- NSURL *fileURL = [sPanel URL];
- if ([[[[webView mainFrame] dataSource] data] writeToURL:fileURL
atomically:YES]) {
- if ([[NSUserDefaults standardUserDefaults]
boolForKey:BDSKUseLocalUrlAndUrlKey])
- [[self publication] setField:BDSKLocalUrlString
toURLValue:fileURL];
- else
- [[self publication] addFileForURL:fileURL autoFile:YES
runScriptHook:NO];
- [[self undoManager] setActionName:NSLocalizedString(@"Edit
Publication", @"Undo action name")];
- } else {
- NSLog(@"Could not write downloaded file.");
- }
- }
-
- [itemTableView reloadData];
- }];
-}
-
- (void)downloadLinkedFileAsLocalUrl:(id)sender{
NSURL *linkURL = (NSURL *)[sender representedObject];
if (isDownloading)
@@ -504,6 +512,27 @@
}
}
+- (void)performCustomMenuAction:(id)sender {
+ NSMenuItem *origItem = [sender representedObject];
+
+ if ([origItem isKindOfClass:[NSMenuItem class]] == NO) {
+ customMenuAction = BDSKWebViewCustomMenuActionNone;
+ NSBeep();
+ return;
+ }
+
+ customMenuAction = [sender tag];
+
+ if (customMenuAction != BDSKWebViewCustomMenuActionNone) {
+ [NSApp sendAction:[origItem action] to:[origItem target]
from:origItem];
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 *
NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ customMenuAction = BDSKWebViewCustomMenuActionNone;
+ });
+ } else {
+ NSBeep();
+ }
+}
+
#pragma mark UndoManager
- (NSUndoManager *)undoManager {
@@ -547,14 +576,6 @@
#pragma mark Private
-// workaround for webview bug, which looses its selection when the focus
changes to another view
-- (void)handleWebViewDidChangeSelection:(NSNotification *)notification{
- NSString *selString = [[[notification object] selectedDOMRange]
toString];
- if ([NSString isEmptyString:selString] || selString == webSelection)
- return;
- webSelection = [[selString
stringByCollapsingAndTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]] copy];
-}
-
- (void)handleFlagsChangedNotification:(NSNotification *)notification{
NSUInteger modifierFlags = [NSEvent standardModifierFlags];
@@ -583,11 +604,25 @@
[itemTableView reloadData];
} else if ([changeKey isEqualToString:BDSKPubTypeString]) {
[self updateTypeAndFields];
- } else {
+ } else if ([changeKey isEqualToString:BDSKRemoteURLString] == NO &&
[changeKey isEqualToString:BDSKLocalFileString] == NO) {
[itemTableView reloadData];
}
}
+- (void)setTemporaryTypeSelectStatusMessage:(BOOL)shouldSet {
+ NSString *message = NSLocalizedString(@"Press \u2318= to select a field.",
@"Status message");
+ if (shouldSet)
+ [statusLine setStringValue:message];
+ else if ([[statusLine stringValue] isEqualToString:message])
+ [statusLine setStringValue:@""];
+
+}
+
+- (void)handleTextSelectionChangedNotification:(NSNotification *)notification{
+ if ([sourceTextView window])
+ [self setTemporaryTypeSelectStatusMessage:[sourceTextView
selectedRange].length > 0];
+}
+
- (void)finalizeChangesPreservingSelection:(BOOL)shouldPreserveSelection{
NSResponder *firstResponder = [[self window] firstResponder];
@@ -611,6 +646,49 @@
#pragma mark Setup
+- (void)setupWebView {
+ WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
+ WKUserContentController *userController = [config userContentController];
+ BDSKScriptMessageHandler *messageHandler = [[BDSKScriptMessageHandler
alloc] init];
+ [messageHandler setDelegate:self];
+ [userController addScriptMessageHandler:messageHandler
name:@"changeSelection"];
+ [userController addScriptMessageHandler:messageHandler name:@"hoverLink"];
+ NSString *jsSource = @""
+ "function changeSelection(){
window.webkit.messageHandlers.changeSelection.postMessage(document.getSelection().toString());
};\n"
+ "document.addEventListener('selectionchange', changeSelection);\n"
+ "function hover(){
window.webkit.messageHandlers.hoverLink.postMessage(this.href); }\n"
+ "function unhover(){
window.webkit.messageHandlers.hoverLink.postMessage(''); }\n"
+ "var links = document.links;\n"
+ "for(var i=0; i<links.length; i++){\n"
+ " links[i].addEventListener('mouseover', hover);\n"
+ " links[i].addEventListener('mouseout', unhover);\n"
+ "}";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:jsSource
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
+ [userController addUserScript:script];
+
+ webView = [[BDSKWKWebView alloc] initWithFrame:[webViewBox bounds]
configuration:config];
+ [webView setNavigationDelegate:self];
+ [webView setUIDelegate:self];
+
+ [webViewBox setEdges:BDSKEveryEdgeMask];
+ [webViewBox setBackgroundColors:nil];
+ [webViewBox setBackgroundView:nil];
+ [webViewBox setContentView:webView];
+
+ [backButton setTarget:webView];
+ [backButton setAction:@selector(goBack:)];
+ [forwardButton setTarget:webView];
+ [forwardButton setAction:@selector(goForward:)];
+}
+
+- (void)clearWebView {
+ [webView setNavigationDelegate:nil];
+ [webView setUIDelegate:nil];
+ [[[webView configuration] userContentController]
removeScriptMessageHandlerForName:@"hoverLink"];
+ [[[webView configuration] userContentController]
removeScriptMessageHandlerForName:@"selectionChanged"];
+ [[[webView configuration] userContentController] removeAllUserScripts];
+}
+
- (void)loadPasteboardData{
NSPasteboard* pb = [NSPasteboard generalPasteboard];
@@ -621,12 +699,10 @@
if([pbType isEqualToString:NSURLPboardType]){
// setup webview and load page
- [self setShowingWebView:YES];
-
NSArray *urls = (NSArray *)[pb propertyListForType:pbType];
- NSURL *url = [NSURL URLWithString:[urls objectAtIndex:0]];
+ NSURL *url = [NSURL URLWithString:[urls firstObject]];
- [webView setURL:url];
+ [self showWebViewWithURL:url];
}else{
@@ -690,7 +766,7 @@
- (void)showWebViewWithURL:(NSURL *)url{
[self setShowingWebView:YES];
- [webView setURL:url];
+ [webView loadRequest:[NSURLRequest requestWithURL:url]];
}
- (void)loadFromFileURL:(NSURL *)url {
@@ -716,23 +792,17 @@
if (showWebView != showingWebView) {
showingWebView = showWebView;
if (showingWebView) {
+ if (webView == nil)
+ [self setupWebView];
[webViewView setFrame:[sourceView frame]];
[splitView replaceSubview:sourceView with:webViewView];
} else {
[splitView replaceSubview:webViewView with:sourceView];
}
+ [self setTemporaryTypeSelectStatusMessage:NO];
}
}
-- (void)setupTypeUI{
-
- // setup the type popup:
- [itemTypeButton removeAllItems];
- [itemTypeButton addItemsWithTitles:[[BDSKTypeManager sharedManager]
types]];
-
- [self updateTypeAndFields];
-}
-
- (void)updateTypeAndFields {
NSString *type = [[self publication] pubType];
@@ -874,9 +944,7 @@
#pragma mark Menu validation
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem{
- if ([menuItem action] == @selector(saveFileAsLocalUrl:)) {
- return NO == [webView isLoading];
- } else if ([menuItem action] == @selector(importFromPasteboardAction:))
{
+ if ([menuItem action] == @selector(importFromPasteboardAction:)) {
[menuItem setTitle:NSLocalizedString(@"Load Clipboard", @"Menu
item title")];
return YES;
} else if ([menuItem action] == @selector(importFromFileAction:)) {
@@ -896,76 +964,205 @@
return YES;
}
-#pragma mark BDSKWebViewDelegate protocol
+#pragma mark WKNavigationDelegate protocol
-- (NSArray *)webView:(WebView *)sender
contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray
*)defaultMenuItems{
- NSMutableArray *menuItems = [NSMutableArray
arrayWithArray:defaultMenuItems];
- NSMenuItem *menuItem;
-
- NSUInteger i = [[menuItems valueForKey:@"tag"] indexOfObject:[NSNumber
numberWithInteger:WebMenuItemTagCopyLinkToClipboard]];
-
- if (i != NSNotFound) {
- NSURL *linkURL = [element objectForKey:WebElementLinkURLKey];
-
- menuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy
Link To Url Field",@"Copy link to url field")
-
action:@selector(copyLinkedLocationAsRemoteUrl:)
- keyEquivalent:@""];
- [menuItem setTarget:self];
- [menuItem setRepresentedObject:linkURL];
- [menuItems insertObject:menuItem atIndex:++i];
-
- menuItem = [[NSMenuItem alloc] initWithTitle:[NSLocalizedString(@"Save
Link As Local File",@"Save link as local file") stringByAppendingEllipsis]
-
action:@selector(downloadLinkedFileAsLocalUrl:)
- keyEquivalent:@""];
- [menuItem setTarget:self];
- [menuItem setRepresentedObject:linkURL];
- [menuItems insertObject:menuItem atIndex:++i];
- }
-
- i = [[menuItems valueForKey:@"tag"] indexOfObject:[NSNumber
numberWithInteger:BDSKWebMenuItemTagAddBookmark]];
-
- if (i == NSNotFound) {
- [menuItems addObject:[NSMenuItem separatorItem]];
- i = [menuItems count];
- }
-
- menuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy
Page Location To Url Field", @"Menu item title")
-
action:@selector(copyLocationAsRemoteUrl:)
- keyEquivalent:@""];
- [menuItem setTarget:self];
- [menuItems insertObject:menuItem atIndex:++i];
-
- menuItem = [[NSMenuItem alloc] initWithTitle:[NSLocalizedString(@"Save
Page As Local File", @"Menu item title") stringByAppendingEllipsis]
-
action:@selector(saveFileAsLocalUrl:)
- keyEquivalent:@""];
- [menuItem setTarget:self];
- [menuItems insertObject:menuItem atIndex:++i];
-
- return menuItems;
+- (void)webView:(WKWebView *)aWebView
didStartProvisionalNavigation:(WKNavigation *)navigation {
+ [self setLoading:YES];
}
-- (void)webView:(WebView *)sender setStatusText:(NSString *)text {
- [statusLine setStringValue:text ?: @""];
+- (void)webView:(WKWebView *)aWebView didFinishNavigation:(WKNavigation
*)navigation {
+ [self setLoading:[webView isLoading]];
+ [backButton setEnabled:[webView canGoBack]];
+ [forwardButton setEnabled:[webView canGoForward]];
}
-- (void)webView:(WebView *)sender didStartLoadForFrame:(WebFrame *)frame {
- [self setLoading:YES];
+- (void)webView:(WKWebView *)aWebView
didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError
*)error {
+ [self setLoading:[webView isLoading]];
+ [backButton setEnabled:[webView canGoBack]];
+ [forwardButton setEnabled:[webView canGoForward]];
}
-- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
+- (void)webView:(WKWebView *)aWebView didFailNavigation:(WKNavigation
*)navigation withError:(NSError *)error {
[self setLoading:[webView isLoading]];
- [backButton setEnabled:[webView canGoBack]];
- [forwardButton setEnabled:[webView canGoForward]];
+ [backButton setEnabled:[webView canGoBack]];
+ [forwardButton setEnabled:[webView canGoForward]];
+}
- [self autoDiscoverDataFromFrame:frame];
+- (void)webView:(WKWebView *)aWebView
decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
+ if ([navigationResponse canShowMIMEType])
+ decisionHandler(WKNavigationResponsePolicyAllow);
+ else if (@available(macOS 11.3, *))
+ decisionHandler(WKNavigationResponsePolicyDownload);
+ else
+ decisionHandler(WKNavigationResponsePolicyCancel);
}
-- (void)webView:(WebView *)sender didFailLoadForFrame:(WebFrame *)frame {
- [self setLoading:[webView isLoading]];
- [backButton setEnabled:[webView canGoBack]];
- [forwardButton setEnabled:[webView canGoForward]];
+- (void)webView:(WKWebView *)aWebView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
preferences:(WKWebpagePreferences *)preferences decisionHandler:(void
(^)(WKNavigationActionPolicy, WKWebpagePreferences *))decisionHandler
API_AVAILABLE(macos(10.15)) {
+ if (@available(macOS 11.3, *)) {
+ if ([navigationAction shouldPerformDownload])
+ decisionHandler(WKNavigationActionPolicyDownload, preferences);
+ else
+ decisionHandler(WKNavigationActionPolicyAllow, preferences);
+ } else {
+ decisionHandler(WKNavigationActionPolicyAllow, preferences);
+ }
}
+- (void)webView:(WKWebView *)aWebView navigationAction:(WKNavigationAction
*)navigationAction didBecomeDownload:(WKDownload *)aDownload
API_AVAILABLE(macos(11.3)) {
+ [[BDSKDownloadManager sharedManager] addDownload:aDownload];
+}
+
+- (void)webView:(WKWebView *)aWebView navigationResponse:(WKNavigationResponse
*)navigationResponse didBecomeDownload:(WKDownload *)aDownload
API_AVAILABLE(macos(11.3)) {
+ [[BDSKDownloadManager sharedManager] addDownload:aDownload];
+}
+
+#pragma mark BDSKUIDelegate protocol
+
+- (NSString *)alertTitleForFrame:(WKFrameInfo *)frame {
+ NSURL *url = [[frame request] URL];
+ NSString *scheme = [url scheme];
+ NSString *host = [url host];
+ if (scheme != nil && host != nil)
+ return [NSString stringWithFormat:@"%@://%@", scheme, host];
+ return NSLocalizedString(@"JavaScript", @"Default JavaScript alert title");
+}
+
+- (void)webView:(WKWebView *)aWebView
runJavaScriptAlertPanelWithMessage:(NSString *)message
initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void
(^)(void))completionHandler {
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:[self alertTitleForFrame:frame]];
+ [alert setInformativeText:message];
+ [alert beginSheetModalForWindow:[self window]
completionHandler:^(NSModalResponse result){
+ completionHandler();
+ }];
+}
+
+- (void)webView:(WKWebView *)aWebView
runJavaScriptConfirmPanelWithMessage:(NSString *)message
initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL
result))completionHandler {
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:[self alertTitleForFrame:frame]];
+ [alert setInformativeText:message];
+ [alert addButtonWithTitle:NSLocalizedString(@"OK", @"Button title")];
+ [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"Button title")];
+ [alert beginSheetModalForWindow:[self window]
completionHandler:^(NSModalResponse result){
+ completionHandler(result == NSAlertFirstButtonReturn);
+ }];
+}
+
+- (WKWebView *)webView:(WKWebView *)aWebView
createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
forNavigationAction:(WKNavigationAction *)navigationAction
windowFeatures:(WKWindowFeatures *)windowFeatures {
+ NSURL *url = [[navigationAction request] URL];
+
+ if (customMenuAction == BDSKWebViewCustomMenuActionOpenLink) {
+ [[NSWorkspace sharedWorkspace] openURLWithDefaultApp:url];
+ } else if (customMenuAction == BDSKWebViewCustomMenuActionBookmarkLink) {
+ [[BDSKBookmarkController sharedBookmarkController]
addBookmarkWithURL:url proposedName:[url lastPathComponent]
modalForWindow:[self window]];
+ } else if (customMenuAction == BDSKWebViewCustomMenuActionAddLink) {
+ if ([[NSUserDefaults standardUserDefaults]
boolForKey:BDSKUseLocalUrlAndUrlKey])
+ [[self publication] setField:[url isFileURL] ? BDSKLocalUrlString
: BDSKUrlString toURLValue:url];
+ else
+ [[self publication] addFileForURL:url autoFile:YES
runScriptHook:NO];
+ [[self undoManager] setActionName:NSLocalizedString(@"Edit
Publication", @"Undo action name")];
+ } else if (customMenuAction == BDSKWebViewCustomMenuActionDownloadLink) {
+ if (isDownloading)
+ return nil;
+ if (url) {
+ download = [[BDSKDownloader sharedDownloader]
startFileDownloadWithRequest:[NSURLRequest requestWithURL:url] delegate:self];
+ [self setDownloading:YES];
+ }
+ if (download == nil) {
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:NSLocalizedString(@"Invalid or Unsupported
URL", @"Message in alert dialog when unable to download file for Local-Url")];
+ [alert setInformativeText:NSLocalizedString(@"The URL to download
is either invalid or unsupported.", @"Informative text in alert dialog")];
+ [alert beginSheetModalForWindow:[self window]
completionHandler:NULL];
+ }
+ } else {
+ [[NSWorkspace sharedWorkspace] openURLWithDefaultApp:url];
+ }
+ return nil;
+}
+
+- (void)webView:(WKWebView *)aWebView willPopUpMenu:(NSMenu *)menu {
+ NSMenuItem *item;
+ NSInteger i = [menu numberOfItems], j = -1;
+ while (i-- > 0) {
+ item = [menu itemAtIndex:i];
+ NSString *itemID = [item identifier];
+ if ([itemID
isEqualToString:@"WKMenuItemIdentifierOpenLinkInNewWindow"]) {
+ NSMenuItem *openLinkItem = item;
+ [menu removeItemAtIndex:i];
+
+ item = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Save
Link As Local File", @"Menu item title")
action:@selector(performCustomMenuAction:) keyEquivalent:@""];
+ [item setTarget:self];
+ [item setTag:BDSKWebViewCustomMenuActionDownloadLink];
+ [item setIdentifier:@"BDSKMenuItemIdentifierDownloadLink"];
+ [item setRepresentedObject:openLinkItem];
+ [menu insertItem:item atIndex:i];
+
+ item = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy
Link To Url Field", @"Menu item title")
action:@selector(performCustomMenuAction:) keyEquivalent:@""];
+ [item setTarget:self];
+ [item setTag:BDSKWebViewCustomMenuActionAddLink];
+ [item setIdentifier:@"BDSKMenuItemIdentifierAddLink"];
+ [item setRepresentedObject:openLinkItem];
+ [menu insertItem:item atIndex:i];
+
+ item = [[NSMenuItem alloc]
initWithTitle:[NSLocalizedString(@"Bookmark Link", @"Menu item title")
stringByAppendingEllipsis] action:@selector(performCustomMenuAction:)
keyEquivalent:@""];
+ [item setTarget:self];
+ [item setTag:BDSKWebViewCustomMenuActionBookmarkLink];
+ [item setIdentifier:@"BDSKMenuItemIdentifierBookmarkLink"];
+ [item setRepresentedObject:openLinkItem];
+ [menu insertItem:item atIndex:i];
+
+ item = [[NSMenuItem alloc] initWithTitle:[NSLocalizedString(@"Open
Link in Browser", @"Menu item title") stringByAppendingEllipsis]
action:@selector(performCustomMenuAction:) keyEquivalent:@""];
+ [item setTarget:self];
+ [item setTag:BDSKWebViewCustomMenuActionOpenLink];
+ [item setIdentifier:@"BDSKMenuItemIdentifierOpenLink"];
+ [item setRepresentedObject:openLinkItem];
+ [menu insertItem:item atIndex:i];
+
+ if (j >= 0)
+ j += 3;
+ } else if ([itemID isEqualToString:@"WKMenuItemIdentifierCopy"] ||
+ [itemID isEqualToString:@"WKMenuItemIdentifierCopyLink"]) {
+ if (j == -1)
+ j = i;
+ }
+ }
+
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ if (j == -1)
+ j = [menu numberOfItems];
+ else
+ j++;
+
+ if (j < [menu numberOfItems] && [[menu itemAtIndex:j] isSeparatorItem] ==
NO)
+ [menu insertItem:[NSMenuItem separatorItem] atIndex:j];
+
+ item = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy Page
Location To Url Field", @"Menu item title")
action:@selector(copyLocationAsRemoteUrl:) keyEquivalent:@""];
+ [item setTarget:self];
+ [item setIdentifier:@"BDSKMenuItemIdentifierCopyLocationToUrl"];
+ [menu insertItem:item atIndex:j];
+
+ item = [[NSMenuItem alloc] initWithTitle:[NSLocalizedString(@"Bookmark
This Page", @"Menu item title") stringByAppendingEllipsis]
action:@selector(addBookmark:) keyEquivalent:@""];
+ [item setTarget:self];
+ [item setIdentifier:@"BDSKMenuItemIdentifierBookmarkPage"];
+ [menu insertItem:item atIndex:j];
+
+ if (j > 0 && [[menu itemAtIndex:j - 1] isSeparatorItem] == NO)
+ [menu insertItem:[NSMenuItem separatorItem] atIndex:j];
+}
+
+#pragma mark BDSKScriptMessageHandlerDelegate protocol
+
+- (void)scriptMessageHandlerWithName:(NSString *)name
didReceiveMessage:(NSString *)body {
+ if ([name isEqualToString:@"hoverLink"]) {
+ [statusLine setStringValue:body ?: @""];
+ } else if ([name isEqualToString:@"changeSelection"]) {
+ if ([NSString isEmptyString:body] == NO)
+ webSelection = [body
stringByCollapsingAndTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]];
+ if ([webView window])
+ [self setTemporaryTypeSelectStatusMessage:[NSString
isEmptyString:body] == NO];
+ }
+}
+
#pragma mark BDSKDownloadDelegate methods
- (void)download:(BDSKDownload *)aDownload
didReceiveExpectedContentLength:(int64_t)length {
@@ -1580,3 +1777,33 @@
}
@end
+
+#pragma mark -
+
+@implementation BDSKWKWebView
+
+@dynamic UIDelegate;
+
+- (void)willOpenMenu:(NSMenu *)menu withEvent:(NSEvent *)event {
+ [super willOpenMenu:menu withEvent:event];
+
+ if ([[self UIDelegate]
respondsToSelector:@selector(webView:willPopUpMenu:)])
+ [[self UIDelegate] webView:self willPopUpMenu:menu];
+}
+
+@end
+
+#pragma mark -
+
+@implementation BDSKScriptMessageHandler
+
+@synthesize delegate;
+
+- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message {
+ NSString *body = [message body];
+ if ([body isKindOfClass:[NSString class]] == NO)
+ body = nil;
+ [delegate scriptMessageHandlerWithName:[message name]
didReceiveMessage:body];
+}
+
+@end
Modified: trunk/bibdesk/de.lproj/Localizable.strings
===================================================================
(Binary files differ)
Modified: trunk/bibdesk/en.lproj/Localizable.strings
===================================================================
(Binary files differ)
Modified: trunk/bibdesk/fr.lproj/Localizable.strings
===================================================================
(Binary files differ)
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
_______________________________________________
Bibdesk-commit mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/bibdesk-commit