BTHLABS-50: Safari Web extension
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl> Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xED",
|
||||
"green" : "0xBA",
|
||||
"red" : "0x1C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon-1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "icon-1024 1.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "tinted"
|
||||
}
|
||||
],
|
||||
"filename" : "icon-1024 2.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-16.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-32 1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-64.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-mac-1024.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 874 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 117 KiB |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x29",
|
||||
"green" : "0x25",
|
||||
"red" : "0x21"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
23
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon-large-128.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-large-128@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-large-128@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon-large-128.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon-large-128@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon-large-128@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 63 KiB |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xDD",
|
||||
"green" : "0x82",
|
||||
"red" : "0x00"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
20
services/apple/Shared (App)/Resources/Base.lproj/Main.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
|
||||
<link rel="stylesheet" href="../Style.css">
|
||||
<script src="../Script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<img src="../icon-mac-384.png" width="128" height="128" alt="HotPocket Icon">
|
||||
<p class="platform-ios">You can turn on Save to Hotpocket Safari extension in Settings.</p>
|
||||
<p class="platform-mac state-unknown">You can turn on Save to Hotpocket extension in Safari Extensions preferences.</p>
|
||||
<p class="platform-mac state-on">Save to Hotpocket extension is currently on. You can turn it off in Safari Extensions preferences.</p>
|
||||
<p class="platform-mac state-off">Save to Hotpocket extension is currently off. You can turn it on in Safari Extensions preferences.</p>
|
||||
<button class="platform-mac open-preferences">Quit and Open Safari Extensions Preferences…</button>
|
||||
</body>
|
||||
</html>
|
||||
24
services/apple/Shared (App)/Resources/Script.js
Normal file
@@ -0,0 +1,24 @@
|
||||
function show(platform, enabled, useSettingsInsteadOfPreferences) {
|
||||
document.body.classList.add(`platform-${platform}`);
|
||||
|
||||
if (useSettingsInsteadOfPreferences) {
|
||||
document.getElementsByClassName('platform-mac state-on')[0].innerText = "Save to Hotpocket extension is currently on. You can turn it off in the Extensions section of Safari Settings.";
|
||||
document.getElementsByClassName('platform-mac state-off')[0].innerText = "Save to Hotpocket extension is currently off. You can turn it on in the Extensions section of Safari Settings.";
|
||||
document.getElementsByClassName('platform-mac state-unknown')[0].innerText = "You can turn on Save to Hotpocket extension in the Extensions section of Safari Settings.";
|
||||
document.getElementsByClassName('platform-mac open-preferences')[0].innerText = "Quit and Open Safari Settings…";
|
||||
}
|
||||
|
||||
if (typeof enabled === "boolean") {
|
||||
document.body.classList.toggle(`state-on`, enabled);
|
||||
document.body.classList.toggle(`state-off`, !enabled);
|
||||
} else {
|
||||
document.body.classList.remove(`state-on`);
|
||||
document.body.classList.remove(`state-off`);
|
||||
}
|
||||
}
|
||||
|
||||
function openPreferences() {
|
||||
webkit.messageHandlers.controller.postMessage("open-preferences");
|
||||
}
|
||||
|
||||
document.querySelector("button.open-preferences").addEventListener("click", openPreferences);
|
||||
63
services/apple/Shared (App)/Resources/Style.css
Normal file
@@ -0,0 +1,63 @@
|
||||
* {
|
||||
-webkit-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
|
||||
--spacing: 20px;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #212529;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
gap: var(--spacing);
|
||||
margin: 0 calc(var(--spacing) * 2);
|
||||
height: 100%;
|
||||
|
||||
font: -apple-system-short-body;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body:not(.platform-mac, .platform-ios) :is(.platform-mac, .platform-ios) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.platform-ios .platform-mac {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.platform-mac .platform-ios {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.platform-ios .platform-mac {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body:not(.state-on, .state-off) :is(.state-on, .state-off) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.state-on :is(.state-off, .state-unknown) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.state-off :is(.state-on, .state-unknown) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 1em;
|
||||
}
|
||||
BIN
services/apple/Shared (App)/Resources/icon-mac-384.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
26
services/apple/Shared (App)/ViewController.h
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// ViewController.h
|
||||
// Shared (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
typedef UIViewController PlatformViewController;
|
||||
|
||||
#elif TARGET_OS_OSX
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
typedef NSViewController PlatformViewController;
|
||||
|
||||
#endif
|
||||
|
||||
@interface ViewController : PlatformViewController
|
||||
|
||||
@end
|
||||
76
services/apple/Shared (App)/ViewController.m
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// ViewController.m
|
||||
// Shared (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import "ViewController.h"
|
||||
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
#import <SafariServices/SafariServices.h>
|
||||
#endif
|
||||
|
||||
static NSString * const extensionBundleIdentifier = @"pl.bthlabs.HotPocket.HotPocket.Extension";
|
||||
|
||||
@interface ViewController () <WKNavigationDelegate, WKScriptMessageHandler>
|
||||
|
||||
@property (nonatomic) IBOutlet WKWebView *webView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
_webView.navigationDelegate = self;
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
_webView.scrollView.scrollEnabled = NO;
|
||||
#endif
|
||||
|
||||
[_webView.configuration.userContentController addScriptMessageHandler:self name:@"controller"];
|
||||
|
||||
[_webView loadFileURL:[NSBundle.mainBundle URLForResource:@"Main" withExtension:@"html"] allowingReadAccessToURL:NSBundle.mainBundle.resourceURL];
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
||||
#if TARGET_OS_IOS
|
||||
[webView evaluateJavaScript:@"show('ios')" completionHandler:nil];
|
||||
#elif TARGET_OS_OSX
|
||||
[webView evaluateJavaScript:@"show('mac')" completionHandler:nil];
|
||||
|
||||
[SFSafariExtensionManager getStateOfSafariExtensionWithIdentifier:extensionBundleIdentifier completionHandler:^(SFSafariExtensionState *state, NSError *error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!state) {
|
||||
// Insert code to inform the user something went wrong.
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *isExtensionEnabledAsString = state.isEnabled ? @"true" : @"false";
|
||||
if (@available(macOS 13, *))
|
||||
[webView evaluateJavaScript:[NSString stringWithFormat:@"show('mac', %@, true)", isExtensionEnabledAsString] completionHandler:nil];
|
||||
else
|
||||
[webView evaluateJavaScript:[NSString stringWithFormat:@"show('mac', %@, false)", isExtensionEnabledAsString] completionHandler:nil];
|
||||
});
|
||||
}];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
|
||||
#if TARGET_OS_OSX
|
||||
if (![message.body isEqualToString:@"open-preferences"])
|
||||
return;
|
||||
|
||||
[SFSafariApplication showPreferencesForExtensionWithIdentifier:extensionBundleIdentifier completionHandler:^(NSError *error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[NSApp terminate:self];
|
||||
});
|
||||
}];
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||