feat: add support for safari
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
dist-safari
|
||||
*.local
|
||||
dist.crx
|
||||
dist.pem
|
||||
@@ -26,3 +27,8 @@ dist.pem
|
||||
*.sw?
|
||||
|
||||
linkwarden.zip
|
||||
|
||||
# Safari / Xcode
|
||||
**/build/
|
||||
**/DerivedData/
|
||||
*.xcuserdata
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Linkwarden",
|
||||
"description": "The browser extension for Linkwarden.",
|
||||
"homepage_url": "https://linkwarden.app/",
|
||||
"version": "1.5.3",
|
||||
"action": {
|
||||
"default_popup": "index.html",
|
||||
"default_icon": {
|
||||
"16": "16.png",
|
||||
"32": "32.png",
|
||||
"48": "48.png",
|
||||
"128": "128.png"
|
||||
},
|
||||
"default_title": "Linkwarden"
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "src/pages/Options/options.html",
|
||||
"browser_style": false
|
||||
},
|
||||
"icons": { "16": "16.png", "32": "32.png", "48": "48.png", "128": "128.png" },
|
||||
"permissions": [
|
||||
"storage",
|
||||
"scripting",
|
||||
"activeTab",
|
||||
"tabs",
|
||||
"contextMenus"
|
||||
],
|
||||
"host_permissions": ["<all_urls>"],
|
||||
"background": {
|
||||
"scripts": ["background.js"],
|
||||
"type": "module"
|
||||
},
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' http: https:;"
|
||||
},
|
||||
"commands": {
|
||||
"_execute_action": {
|
||||
"suggested_key": { "default": "Ctrl+Shift+F", "mac": "Command+Shift+Y" }
|
||||
}
|
||||
},
|
||||
"browser_specific_settings": {
|
||||
"safari": {
|
||||
"strict_min_version": "15.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build && cp ./manifest.json dist/manifest.json",
|
||||
"build:safari": "tsc && vite build --outDir dist-safari && cp ./manifest.safari.json dist-safari/manifest.json && cp public/*.png dist-safari/",
|
||||
"convert:safari": "xcrun safari-web-extension-converter dist-safari/ --project-location safari/ --app-name Linkwarden --bundle-identifier app.linkwarden.safari-extension --swift --macos-only --no-prompt --no-open",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.Safari.web-extension</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SafariWebExtensionHandler</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// SafariWebExtensionHandler.swift
|
||||
// Linkwarden Extension
|
||||
//
|
||||
// Created by Daniel on 2026-04-23.
|
||||
//
|
||||
|
||||
import SafariServices
|
||||
import os.log
|
||||
|
||||
class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
||||
|
||||
func beginRequest(with context: NSExtensionContext) {
|
||||
let request = context.inputItems.first as? NSExtensionItem
|
||||
|
||||
let profile: UUID?
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
profile = request?.userInfo?[SFExtensionProfileKey] as? UUID
|
||||
} else {
|
||||
profile = request?.userInfo?["profile"] as? UUID
|
||||
}
|
||||
|
||||
let message: Any?
|
||||
if #available(iOS 15.0, macOS 11.0, *) {
|
||||
message = request?.userInfo?[SFExtensionMessageKey]
|
||||
} else {
|
||||
message = request?.userInfo?["message"]
|
||||
}
|
||||
|
||||
os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", String(describing: message), profile?.uuidString ?? "none")
|
||||
|
||||
let response = NSExtensionItem()
|
||||
if #available(iOS 15.0, macOS 11.0, *) {
|
||||
response.userInfo = [ SFExtensionMessageKey: [ "echo": message ] ]
|
||||
} else {
|
||||
response.userInfo = [ "message": [ "echo": message ] ]
|
||||
}
|
||||
|
||||
context.completeRequest(returningItems: [ response ], completionHandler: nil)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,647 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
65E051CD2F9AFEE100628D48 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E051CC2F9AFEE100628D48 /* AppDelegate.swift */; };
|
||||
65E051D12F9AFEE200628D48 /* Main.html in Resources */ = {isa = PBXBuildFile; fileRef = 65E051CF2F9AFEE100628D48 /* Main.html */; };
|
||||
65E051D32F9AFEE200628D48 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 65E051D22F9AFEE200628D48 /* Icon.png */; };
|
||||
65E051D52F9AFEE200628D48 /* Style.css in Resources */ = {isa = PBXBuildFile; fileRef = 65E051D42F9AFEE200628D48 /* Style.css */; };
|
||||
65E051D72F9AFEE200628D48 /* Script.js in Resources */ = {isa = PBXBuildFile; fileRef = 65E051D62F9AFEE200628D48 /* Script.js */; };
|
||||
65E051D92F9AFEE200628D48 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E051D82F9AFEE200628D48 /* ViewController.swift */; };
|
||||
65E051DC2F9AFEE200628D48 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65E051DA2F9AFEE200628D48 /* Main.storyboard */; };
|
||||
65E051DE2F9AFEE300628D48 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 65E051DD2F9AFEE300628D48 /* Assets.xcassets */; };
|
||||
65E051E52F9AFEE300628D48 /* Linkwarden Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 65E051E42F9AFEE300628D48 /* Linkwarden Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
65E051EA2F9AFEE300628D48 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E051E92F9AFEE300628D48 /* SafariWebExtensionHandler.swift */; };
|
||||
65E052012F9AFEE300628D48 /* 48.png in Resources */ = {isa = PBXBuildFile; fileRef = 65E051F62F9AFEE300628D48 /* 48.png */; };
|
||||
65E052022F9AFEE300628D48 /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = 65E051F72F9AFEE300628D48 /* index.html */; };
|
||||
65E052032F9AFEE300628D48 /* background.js in Resources */ = {isa = PBXBuildFile; fileRef = 65E051F82F9AFEE300628D48 /* background.js */; };
|
||||
65E052042F9AFEE300628D48 /* 128.png in Resources */ = {isa = PBXBuildFile; fileRef = 65E051F92F9AFEE300628D48 /* 128.png */; };
|
||||
65E052052F9AFEE300628D48 /* options.js in Resources */ = {isa = PBXBuildFile; fileRef = 65E051FA2F9AFEE300628D48 /* options.js */; };
|
||||
65E052062F9AFEE300628D48 /* 16.png in Resources */ = {isa = PBXBuildFile; fileRef = 65E051FB2F9AFEE300628D48 /* 16.png */; };
|
||||
65E052072F9AFEE300628D48 /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 65E051FC2F9AFEE300628D48 /* main.js */; };
|
||||
65E052082F9AFEE300628D48 /* 32.png in Resources */ = {isa = PBXBuildFile; fileRef = 65E051FD2F9AFEE300628D48 /* 32.png */; };
|
||||
65E052092F9AFEE300628D48 /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 65E051FE2F9AFEE300628D48 /* manifest.json */; };
|
||||
65E0520A2F9AFEE300628D48 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 65E051FF2F9AFEE300628D48 /* assets */; };
|
||||
65E0520B2F9AFEE300628D48 /* src in Resources */ = {isa = PBXBuildFile; fileRef = 65E052002F9AFEE300628D48 /* src */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
65E051E62F9AFEE300628D48 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 65E051C12F9AFEE100628D48 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 65E051E32F9AFEE300628D48;
|
||||
remoteInfo = "Linkwarden Extension";
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
65E051F12F9AFEE300628D48 /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
65E051E52F9AFEE300628D48 /* Linkwarden Extension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
65E051C92F9AFEE100628D48 /* Linkwarden.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Linkwarden.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
65E051CC2F9AFEE100628D48 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
65E051D02F9AFEE100628D48 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = Base; path = Base.lproj/Main.html; sourceTree = "<group>"; };
|
||||
65E051D22F9AFEE200628D48 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = "<group>"; };
|
||||
65E051D42F9AFEE200628D48 /* Style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = Style.css; sourceTree = "<group>"; };
|
||||
65E051D62F9AFEE200628D48 /* Script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Script.js; sourceTree = "<group>"; };
|
||||
65E051D82F9AFEE200628D48 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
65E051DB2F9AFEE200628D48 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
65E051DD2F9AFEE300628D48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
65E051DF2F9AFEE300628D48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
65E051E42F9AFEE300628D48 /* Linkwarden Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Linkwarden Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
65E051E92F9AFEE300628D48 /* SafariWebExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariWebExtensionHandler.swift; sourceTree = "<group>"; };
|
||||
65E051EB2F9AFEE300628D48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
65E051F62F9AFEE300628D48 /* 48.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = 48.png; path = "../../../dist-safari/48.png"; sourceTree = "<group>"; };
|
||||
65E051F72F9AFEE300628D48 /* index.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = index.html; path = "../../../dist-safari/index.html"; sourceTree = "<group>"; };
|
||||
65E051F82F9AFEE300628D48 /* background.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = background.js; path = "../../../dist-safari/background.js"; sourceTree = "<group>"; };
|
||||
65E051F92F9AFEE300628D48 /* 128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = 128.png; path = "../../../dist-safari/128.png"; sourceTree = "<group>"; };
|
||||
65E051FA2F9AFEE300628D48 /* options.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = options.js; path = "../../../dist-safari/options.js"; sourceTree = "<group>"; };
|
||||
65E051FB2F9AFEE300628D48 /* 16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = 16.png; path = "../../../dist-safari/16.png"; sourceTree = "<group>"; };
|
||||
65E051FC2F9AFEE300628D48 /* main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = main.js; path = "../../../dist-safari/main.js"; sourceTree = "<group>"; };
|
||||
65E051FD2F9AFEE300628D48 /* 32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = 32.png; path = "../../../dist-safari/32.png"; sourceTree = "<group>"; };
|
||||
65E051FE2F9AFEE300628D48 /* manifest.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = manifest.json; path = "../../../dist-safari/manifest.json"; sourceTree = "<group>"; };
|
||||
65E051FF2F9AFEE300628D48 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = "../../../dist-safari/assets"; sourceTree = "<group>"; };
|
||||
65E052002F9AFEE300628D48 /* src */ = {isa = PBXFileReference; lastKnownFileType = folder; name = src; path = "../../../dist-safari/src"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
65E051C62F9AFEE100628D48 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
65E051E12F9AFEE300628D48 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
65E051C02F9AFEE100628D48 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
65E051CB2F9AFEE100628D48 /* Linkwarden */,
|
||||
65E051E82F9AFEE300628D48 /* Linkwarden Extension */,
|
||||
65E051CA2F9AFEE100628D48 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
65E051CA2F9AFEE100628D48 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
65E051C92F9AFEE100628D48 /* Linkwarden.app */,
|
||||
65E051E42F9AFEE300628D48 /* Linkwarden Extension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
65E051CB2F9AFEE100628D48 /* Linkwarden */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
65E051CC2F9AFEE100628D48 /* AppDelegate.swift */,
|
||||
65E051D82F9AFEE200628D48 /* ViewController.swift */,
|
||||
65E051DA2F9AFEE200628D48 /* Main.storyboard */,
|
||||
65E051DD2F9AFEE300628D48 /* Assets.xcassets */,
|
||||
65E051DF2F9AFEE300628D48 /* Info.plist */,
|
||||
65E051CE2F9AFEE100628D48 /* Resources */,
|
||||
);
|
||||
path = Linkwarden;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
65E051CE2F9AFEE100628D48 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
65E051CF2F9AFEE100628D48 /* Main.html */,
|
||||
65E051D22F9AFEE200628D48 /* Icon.png */,
|
||||
65E051D42F9AFEE200628D48 /* Style.css */,
|
||||
65E051D62F9AFEE200628D48 /* Script.js */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
65E051E82F9AFEE300628D48 /* Linkwarden Extension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
65E051F52F9AFEE300628D48 /* Resources */,
|
||||
65E051E92F9AFEE300628D48 /* SafariWebExtensionHandler.swift */,
|
||||
65E051EB2F9AFEE300628D48 /* Info.plist */,
|
||||
);
|
||||
path = "Linkwarden Extension";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
65E051F52F9AFEE300628D48 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
65E051F62F9AFEE300628D48 /* 48.png */,
|
||||
65E051F72F9AFEE300628D48 /* index.html */,
|
||||
65E051F82F9AFEE300628D48 /* background.js */,
|
||||
65E051F92F9AFEE300628D48 /* 128.png */,
|
||||
65E051FA2F9AFEE300628D48 /* options.js */,
|
||||
65E051FB2F9AFEE300628D48 /* 16.png */,
|
||||
65E051FC2F9AFEE300628D48 /* main.js */,
|
||||
65E051FD2F9AFEE300628D48 /* 32.png */,
|
||||
65E051FE2F9AFEE300628D48 /* manifest.json */,
|
||||
65E051FF2F9AFEE300628D48 /* assets */,
|
||||
65E052002F9AFEE300628D48 /* src */,
|
||||
);
|
||||
name = Resources;
|
||||
path = "Linkwarden Extension";
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
65E051C82F9AFEE100628D48 /* Linkwarden */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 65E051F22F9AFEE300628D48 /* Build configuration list for PBXNativeTarget "Linkwarden" */;
|
||||
buildPhases = (
|
||||
65E051C52F9AFEE100628D48 /* Sources */,
|
||||
65E051C62F9AFEE100628D48 /* Frameworks */,
|
||||
65E051C72F9AFEE100628D48 /* Resources */,
|
||||
65E051F12F9AFEE300628D48 /* Embed Foundation Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
65E051E72F9AFEE300628D48 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Linkwarden;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = Linkwarden;
|
||||
productReference = 65E051C92F9AFEE100628D48 /* Linkwarden.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
65E051E32F9AFEE300628D48 /* Linkwarden Extension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 65E051EE2F9AFEE300628D48 /* Build configuration list for PBXNativeTarget "Linkwarden Extension" */;
|
||||
buildPhases = (
|
||||
65E051E02F9AFEE300628D48 /* Sources */,
|
||||
65E051E12F9AFEE300628D48 /* Frameworks */,
|
||||
65E051E22F9AFEE300628D48 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Linkwarden Extension";
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = "Linkwarden Extension";
|
||||
productReference = 65E051E42F9AFEE300628D48 /* Linkwarden Extension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
65E051C12F9AFEE100628D48 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 2610;
|
||||
LastUpgradeCheck = 2610;
|
||||
TargetAttributes = {
|
||||
65E051C82F9AFEE100628D48 = {
|
||||
CreatedOnToolsVersion = 26.1;
|
||||
};
|
||||
65E051E32F9AFEE300628D48 = {
|
||||
CreatedOnToolsVersion = 26.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 65E051C42F9AFEE100628D48 /* Build configuration list for PBXProject "Linkwarden" */;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 65E051C02F9AFEE100628D48;
|
||||
minimizedProjectReferenceProxies = 1;
|
||||
preferredProjectObjectVersion = 77;
|
||||
productRefGroup = 65E051CA2F9AFEE100628D48 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
65E051C82F9AFEE100628D48 /* Linkwarden */,
|
||||
65E051E32F9AFEE300628D48 /* Linkwarden Extension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
65E051C72F9AFEE100628D48 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
65E051D32F9AFEE200628D48 /* Icon.png in Resources */,
|
||||
65E051DC2F9AFEE200628D48 /* Main.storyboard in Resources */,
|
||||
65E051D72F9AFEE200628D48 /* Script.js in Resources */,
|
||||
65E051D12F9AFEE200628D48 /* Main.html in Resources */,
|
||||
65E051DE2F9AFEE300628D48 /* Assets.xcassets in Resources */,
|
||||
65E051D52F9AFEE200628D48 /* Style.css in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
65E051E22F9AFEE300628D48 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
65E052012F9AFEE300628D48 /* 48.png in Resources */,
|
||||
65E052042F9AFEE300628D48 /* 128.png in Resources */,
|
||||
65E052062F9AFEE300628D48 /* 16.png in Resources */,
|
||||
65E052032F9AFEE300628D48 /* background.js in Resources */,
|
||||
65E052092F9AFEE300628D48 /* manifest.json in Resources */,
|
||||
65E0520A2F9AFEE300628D48 /* assets in Resources */,
|
||||
65E052072F9AFEE300628D48 /* main.js in Resources */,
|
||||
65E0520B2F9AFEE300628D48 /* src in Resources */,
|
||||
65E052022F9AFEE300628D48 /* index.html in Resources */,
|
||||
65E052082F9AFEE300628D48 /* 32.png in Resources */,
|
||||
65E052052F9AFEE300628D48 /* options.js in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
65E051C52F9AFEE100628D48 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
65E051D92F9AFEE200628D48 /* ViewController.swift in Sources */,
|
||||
65E051CD2F9AFEE100628D48 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
65E051E02F9AFEE300628D48 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
65E051EA2F9AFEE300628D48 /* SafariWebExtensionHandler.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
65E051E72F9AFEE300628D48 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 65E051E32F9AFEE300628D48 /* Linkwarden Extension */;
|
||||
targetProxy = 65E051E62F9AFEE300628D48 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
65E051CF2F9AFEE100628D48 /* Main.html */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
65E051D02F9AFEE100628D48 /* Base */,
|
||||
);
|
||||
name = Main.html;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
65E051DA2F9AFEE200628D48 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
65E051DB2F9AFEE200628D48 /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
65E051EC2F9AFEE300628D48 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 26.1;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
65E051ED2F9AFEE300628D48 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 26.1;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
65E051EF2F9AFEE300628D48 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = FZ44882Y36;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_USER_SELECTED_FILES = readonly;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Linkwarden Extension/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Linkwarden Extension";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.5.3;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.linkwarden.extension.safari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
65E051F02F9AFEE300628D48 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = FZ44882Y36;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_USER_SELECTED_FILES = readonly;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Linkwarden Extension/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Linkwarden Extension";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
"INFOPLIST_KEY_NSHumanReadableCopyright[sdk=*]" = "Copyright © 2026 Linkwarden. All rights reserved.";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.5.3;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.linkwarden.extension.safari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
65E051F32F9AFEE300628D48 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = FZ44882Y36;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||
ENABLE_USER_SELECTED_FILES = readonly;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Linkwarden/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Linkwarden;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
INFOPLIST_KEY_NSMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.5.3;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
"-framework",
|
||||
WebKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.linkwarden.extension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
65E051F42F9AFEE300628D48 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = FZ44882Y36;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||
ENABLE_USER_SELECTED_FILES = readonly;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Linkwarden/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Linkwarden;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
"INFOPLIST_KEY_NSHumanReadableCopyright[sdk=*]" = "Copyright © 2026 Linkwarden. All rights reserved.";
|
||||
INFOPLIST_KEY_NSMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.5.3;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
"-framework",
|
||||
WebKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.linkwarden.extension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
65E051C42F9AFEE100628D48 /* Build configuration list for PBXProject "Linkwarden" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
65E051EC2F9AFEE300628D48 /* Debug */,
|
||||
65E051ED2F9AFEE300628D48 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
65E051EE2F9AFEE300628D48 /* Build configuration list for PBXNativeTarget "Linkwarden Extension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
65E051EF2F9AFEE300628D48 /* Debug */,
|
||||
65E051F02F9AFEE300628D48 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
65E051F22F9AFEE300628D48 /* Build configuration list for PBXNativeTarget "Linkwarden" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
65E051F32F9AFEE300628D48 /* Debug */,
|
||||
65E051F42F9AFEE300628D48 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 65E051C12F9AFEE100628D48 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2610"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "65E051C82F9AFEE100628D48"
|
||||
BuildableName = "Linkwarden.app"
|
||||
BlueprintName = "Linkwarden"
|
||||
ReferencedContainer = "container:Linkwarden.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "65E051C82F9AFEE100628D48"
|
||||
BuildableName = "Linkwarden.app"
|
||||
BlueprintName = "Linkwarden"
|
||||
ReferencedContainer = "container:Linkwarden.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "65E051C82F9AFEE100628D48"
|
||||
BuildableName = "Linkwarden.app"
|
||||
BlueprintName = "Linkwarden"
|
||||
ReferencedContainer = "container:Linkwarden.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>Linkwarden.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>65E051C82F9AFEE100628D48</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// Linkwarden
|
||||
//
|
||||
// Created by Daniel on 2026-04-23.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
@main
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
// Override point for customization after application launch.
|
||||
}
|
||||
|
||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "mac-icon-16@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "mac-icon-16@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "mac-icon-32@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "mac-icon-32@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "mac-icon-128@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "mac-icon-128@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "mac-icon-256@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "mac-icon-256@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "mac-icon-512@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "mac-icon-512@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 769 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 280 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19085" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19085"/>
|
||||
<plugIn identifier="com.apple.WebKit2IBPlugin" version="19085"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
<scene sceneID="JPo-4y-FX3">
|
||||
<objects>
|
||||
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
||||
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="Linkwarden" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Linkwarden" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About Linkwarden" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Hide Linkwarden" keyEquivalent="h" id="Olw-nP-bQN">
|
||||
<connections>
|
||||
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||
<menuItem title="Quit Linkwarden" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="wpr-3q-Mcd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
||||
<items>
|
||||
<menuItem title="Linkwarden Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||
<connections>
|
||||
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
||||
</connections>
|
||||
</application>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModuleProvider="target"/>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="76" y="-134"/>
|
||||
</scene>
|
||||
<!--Window Controller-->
|
||||
<scene sceneID="R2V-B0-nI4">
|
||||
<objects>
|
||||
<windowController showSeguePresentationStyle="single" id="B8D-0N-5wS" sceneMemberID="viewController">
|
||||
<window key="window" title="Linkwarden" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" animationBehavior="default" id="IQv-IB-iLA">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<windowCollectionBehavior key="collectionBehavior" fullScreenNone="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="425" height="325"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
|
||||
</connections>
|
||||
</window>
|
||||
<connections>
|
||||
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
|
||||
</connections>
|
||||
</windowController>
|
||||
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="250"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="hIz-AP-VOD">
|
||||
<objects>
|
||||
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="m2S-Jp-Qdl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<wkWebView wantsLayer="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eOr-cG-IQY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<wkWebViewConfiguration key="configuration">
|
||||
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
|
||||
<wkPreferences key="preferences"/>
|
||||
</wkWebViewConfiguration>
|
||||
</wkWebView>
|
||||
</subviews>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="webView" destination="eOr-cG-IQY" id="GFe-mU-dBY"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="655"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SFSafariWebExtensionConverterVersion</key>
|
||||
<string>26.1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,19 @@
|
||||
<!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.png" width="128" height="128" alt="Linkwarden Icon">
|
||||
<p class="state-unknown">You can turn on Linkwarden’s extension in Safari Extensions preferences.</p>
|
||||
<p class="state-on">Linkwarden’s extension is currently on. You can turn it off in Safari Extensions preferences.</p>
|
||||
<p class="state-off">Linkwarden’s extension is currently off. You can turn it on in Safari Extensions preferences.</p>
|
||||
<button class="open-preferences">Quit and Open Safari Extensions Preferences…</button>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,22 @@
|
||||
function show(enabled, useSettingsInsteadOfPreferences) {
|
||||
if (useSettingsInsteadOfPreferences) {
|
||||
document.getElementsByClassName('state-on')[0].innerText = "Linkwarden’s extension is currently on. You can turn it off in the Extensions section of Safari Settings.";
|
||||
document.getElementsByClassName('state-off')[0].innerText = "Linkwarden’s extension is currently off. You can turn it on in the Extensions section of Safari Settings.";
|
||||
document.getElementsByClassName('state-unknown')[0].innerText = "You can turn on Linkwarden’s extension in the Extensions section of Safari Settings.";
|
||||
document.getElementsByClassName('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);
|
||||
@@ -0,0 +1,45 @@
|
||||
* {
|
||||
-webkit-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
|
||||
--spacing: 20px;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
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(.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;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// Linkwarden
|
||||
//
|
||||
// Created by Daniel on 2026-04-23.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import SafariServices
|
||||
import WebKit
|
||||
|
||||
let extensionBundleIdentifier = "app.linkwarden.safari-extension.Extension"
|
||||
|
||||
class ViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHandler {
|
||||
|
||||
@IBOutlet var webView: WKWebView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.webView.navigationDelegate = self
|
||||
|
||||
self.webView.configuration.userContentController.add(self, name: "controller")
|
||||
|
||||
self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!)
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in
|
||||
guard let state = state, error == nil else {
|
||||
// Insert code to inform the user that something went wrong.
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if #available(macOS 13, *) {
|
||||
webView.evaluateJavaScript("show(\(state.isEnabled), true)")
|
||||
} else {
|
||||
webView.evaluateJavaScript("show(\(state.isEnabled), false)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||
if (message.body as! String != "open-preferences") {
|
||||
return;
|
||||
}
|
||||
|
||||
SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in
|
||||
DispatchQueue.main.async {
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -72,21 +72,60 @@ const drawImagesOnCanvas = async (
|
||||
};
|
||||
|
||||
async function executeScript(tabId: number, func: any, args: any[] = []) {
|
||||
if (typeof chrome.scripting !== 'undefined') {
|
||||
if (
|
||||
typeof chrome !== 'undefined' &&
|
||||
typeof chrome.scripting !== 'undefined'
|
||||
) {
|
||||
const results = await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
func,
|
||||
args,
|
||||
});
|
||||
return results[0]?.result;
|
||||
} else {
|
||||
}
|
||||
|
||||
const results = await browser.tabs.executeScript(tabId, {
|
||||
code: `(${func})(${args.map((arg) => JSON.stringify(arg)).join(',')})`,
|
||||
});
|
||||
return results[0];
|
||||
}
|
||||
|
||||
async function safeExecuteScript(tabId: number, func: any, args: any[] = []) {
|
||||
try {
|
||||
await executeScript(tabId, func, args);
|
||||
} catch {
|
||||
// Best effort cleanup for browsers that reject script execution.
|
||||
}
|
||||
}
|
||||
|
||||
function dataUrlToBlob(dataUrl: string): Blob {
|
||||
const [meta = '', encodedData = ''] = dataUrl.split(',', 2);
|
||||
const mimeType = meta.match(/^data:(.*?)(;base64)?$/)?.[1] ?? 'image/png';
|
||||
|
||||
if (meta.includes(';base64')) {
|
||||
const binary = atob(encodedData);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let index = 0; index < binary.length; index++) {
|
||||
bytes[index] = binary.charCodeAt(index);
|
||||
}
|
||||
return new Blob([bytes], { type: mimeType });
|
||||
}
|
||||
|
||||
return new Blob([decodeURIComponent(encodedData)], { type: mimeType });
|
||||
}
|
||||
|
||||
async function captureVisibleTabScreenshot(): Promise<Blob> {
|
||||
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
const tab = tabs[0];
|
||||
if (!tab || !tab.id) {
|
||||
throw new Error('Unable to get the current tab.');
|
||||
}
|
||||
const dataUrl = await browser.tabs.captureVisibleTab(tab.windowId!, {
|
||||
format: 'png',
|
||||
});
|
||||
return dataUrlToBlob(dataUrl);
|
||||
}
|
||||
|
||||
async function captureFullPageScreenshot(): Promise<Blob> {
|
||||
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
const tab = tabs[0];
|
||||
@@ -218,8 +257,7 @@ async function captureFullPageScreenshot(): Promise<Blob> {
|
||||
const dataUrl = await browser.tabs.captureVisibleTab(tab.windowId!, {
|
||||
format: 'png',
|
||||
});
|
||||
const blob = await fetch(dataUrl).then((res) => res.blob());
|
||||
blobs.push(blob);
|
||||
blobs.push(dataUrlToBlob(dataUrl));
|
||||
}
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
@@ -235,11 +273,30 @@ async function captureFullPageScreenshot(): Promise<Blob> {
|
||||
dpr
|
||||
);
|
||||
|
||||
await executeScript(tab.id, removeHideScrollbarClass);
|
||||
await executeScript(tab.id, restoreFixedElements, [originalStyles]);
|
||||
await executeScript(tab.id, removeDisableSmoothScrollbarClass);
|
||||
await safeExecuteScript(tab.id, removeHideScrollbarClass);
|
||||
await safeExecuteScript(tab.id, restoreFixedElements, [originalStyles]);
|
||||
await safeExecuteScript(tab.id, removeDisableSmoothScrollbarClass);
|
||||
|
||||
return resultBlob;
|
||||
}
|
||||
|
||||
export default captureFullPageScreenshot;
|
||||
async function captureScreenshot(): Promise<Blob> {
|
||||
try {
|
||||
return await captureFullPageScreenshot();
|
||||
} catch (fullPageError) {
|
||||
console.warn(
|
||||
'Full-page screenshot failed, falling back to visible tab capture.',
|
||||
fullPageError
|
||||
);
|
||||
try {
|
||||
return await captureVisibleTabScreenshot();
|
||||
} catch (visibleTabError) {
|
||||
console.error('Visible tab screenshot capture failed.', visibleTabError);
|
||||
throw new Error(
|
||||
'Screenshot capture is not available for this page.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default captureScreenshot;
|
||||
|
||||
@@ -57,6 +57,24 @@ export function openOptions() {
|
||||
getBrowser().runtime.openOptionsPage();
|
||||
}
|
||||
|
||||
export function isSafari(): boolean {
|
||||
try {
|
||||
return /^safari-web-extension:/.test(getBrowser().runtime.getURL(''));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function hasAPI(api: string): boolean {
|
||||
const b = getBrowser();
|
||||
let obj: any = b;
|
||||
for (const part of api.split('.')) {
|
||||
if (!obj || typeof obj[part] === 'undefined') return false;
|
||||
obj = obj[part];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function updateBadge(tabId: number | undefined) {
|
||||
if (!tabId) return;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
getBrowser,
|
||||
getCurrentTabInfo,
|
||||
hasAPI,
|
||||
isSafari,
|
||||
updateBadge,
|
||||
} from '../../@/lib/utils.ts';
|
||||
// import BookmarkTreeNode = chrome.bookmarks.BookmarkTreeNode;
|
||||
@@ -240,7 +242,7 @@ async function genericOnClick(
|
||||
}
|
||||
default:
|
||||
// Handle cases where sync is enabled or not
|
||||
if (syncBookmarks) {
|
||||
if (syncBookmarks && hasAPI('bookmarks.create')) {
|
||||
browser.bookmarks.create({
|
||||
parentId: '1',
|
||||
title: tab.title,
|
||||
@@ -277,15 +279,9 @@ async function genericOnClick(
|
||||
}
|
||||
browser.runtime.onInstalled.addListener(async function () {
|
||||
// Create one test item for each context type.
|
||||
const contexts: ContextType[] = [
|
||||
'page',
|
||||
'selection',
|
||||
'link',
|
||||
'editable',
|
||||
'image',
|
||||
'video',
|
||||
'audio',
|
||||
];
|
||||
const contexts: ContextType[] = isSafari()
|
||||
? ['page', 'selection', 'link']
|
||||
: ['page', 'selection', 'link', 'editable', 'image', 'video', 'audio'];
|
||||
for (const context of contexts) {
|
||||
const title: string = 'Add link to Linkwarden';
|
||||
browser.contextMenus.create({
|
||||
@@ -346,9 +342,10 @@ browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
|
||||
}
|
||||
})();
|
||||
|
||||
// Omnibox implementation
|
||||
// Omnibox implementation (not available in Safari)
|
||||
|
||||
browser.omnibox.onInputStarted.addListener(async () => {
|
||||
if (hasAPI('omnibox.onInputStarted')) {
|
||||
browser.omnibox.onInputStarted.addListener(async () => {
|
||||
const configured = await isConfigured();
|
||||
const description = configured
|
||||
? 'Search links in linkwarden'
|
||||
@@ -357,9 +354,9 @@ browser.omnibox.onInputStarted.addListener(async () => {
|
||||
browser.omnibox.setDefaultSuggestion({
|
||||
description: description,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
browser.omnibox.onInputChanged.addListener(
|
||||
browser.omnibox.onInputChanged.addListener(
|
||||
async (
|
||||
text: string,
|
||||
suggest: (arg0: { content: string; description: string }[]) => void
|
||||
@@ -384,11 +381,11 @@ browser.omnibox.onInputChanged.addListener(
|
||||
});
|
||||
suggest(bookmarkSuggestions);
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
// This part was taken https://github.com/sissbruecker/linkding-extension/blob/master/src/background.js Thanks to @sissbruecker
|
||||
// This part was taken https://github.com/sissbruecker/linkding-extension/blob/master/src/background.js Thanks to @sissbruecker
|
||||
|
||||
browser.omnibox.onInputEntered.addListener(
|
||||
browser.omnibox.onInputEntered.addListener(
|
||||
async (content: string, disposition: OnInputEnteredDisposition) => {
|
||||
if (!(await isConfigured()) || !content) {
|
||||
return;
|
||||
@@ -421,4 +418,5 @@ browser.omnibox.onInputEntered.addListener(
|
||||
break;
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||