@@ -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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"name": "Linkwarden",
|
||||
"description": "The browser extension for Linkwarden.",
|
||||
"homepage_url": "https://linkwarden.app/",
|
||||
"version": "1.5.1",
|
||||
"version": "1.5.4",
|
||||
"action": {
|
||||
"default_popup": "index.html",
|
||||
"default_icon": {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Linkwarden",
|
||||
"description": "The browser extension for Linkwarden.",
|
||||
"homepage_url": "https://linkwarden.app/",
|
||||
"version": "1.5.4",
|
||||
"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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,15 +17,15 @@ import { Button } from './ui/Button.tsx';
|
||||
import { TagInput } from './TagInput.tsx';
|
||||
import { Textarea } from './ui/Textarea.tsx';
|
||||
import { getCurrentTabInfo, updateBadge } from '../lib/utils.ts';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { getConfig, isConfigured as getIsConfigured } from '../lib/config.ts';
|
||||
import { checkLinkExists, postLink } from '../lib/actions/links.ts';
|
||||
import { AxiosError } from 'axios';
|
||||
import { toast } from '../../hooks/use-toast.ts';
|
||||
import { Toaster } from './ui/Toaster.tsx';
|
||||
import { getCollections } from '../lib/actions/collections.ts';
|
||||
import { getTags } from '../lib/actions/tags.ts';
|
||||
import { getShouldUseTagSearch, getTags } from '../lib/actions/tags.ts';
|
||||
import { ExternalLink, X } from 'lucide-react';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from './ui/Popover.tsx';
|
||||
import { CaretSortIcon } from '@radix-ui/react-icons';
|
||||
@@ -44,6 +44,7 @@ const BookmarkForm = () => {
|
||||
const [openCollections, setOpenCollections] = useState<boolean>(false);
|
||||
const [uploadImage, setUploadImage] = useState<boolean>(false);
|
||||
const [state, setState] = useState<'capturing' | 'uploading' | null>(null);
|
||||
const [tagSearch, setTagSearch] = useState<string>('');
|
||||
|
||||
const [isConfigured, setIsConfigured] = useState(false);
|
||||
const [isDuplicate, setIsDuplicate] = useState(false);
|
||||
@@ -195,24 +196,46 @@ const BookmarkForm = () => {
|
||||
enabled: isConfigured,
|
||||
});
|
||||
|
||||
const {
|
||||
isLoading: loadingTags,
|
||||
data: tags,
|
||||
error: tagsError,
|
||||
} = useQuery({
|
||||
queryKey: ['tags'],
|
||||
queryFn: async () => {
|
||||
const response = await getTags(
|
||||
const { data: shouldUseTagSearch = false } = useQuery({
|
||||
queryKey: ['tag-search-support', config?.baseUrl, config?.apiKey],
|
||||
queryFn: async () =>
|
||||
await getShouldUseTagSearch(
|
||||
config?.baseUrl as string,
|
||||
config?.apiKey as string
|
||||
),
|
||||
enabled: isConfigured && openOptions,
|
||||
});
|
||||
const effectiveTagSearch = shouldUseTagSearch ? tagSearch : '';
|
||||
const {
|
||||
isLoading: loadingTags,
|
||||
data: tagsData,
|
||||
error: tagsError,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
} = useInfiniteQuery(
|
||||
['tags', config?.baseUrl, config?.apiKey, effectiveTagSearch],
|
||||
async ({ pageParam = 0 }) => {
|
||||
return await getTags(
|
||||
config?.baseUrl as string,
|
||||
config?.apiKey as string,
|
||||
pageParam,
|
||||
effectiveTagSearch
|
||||
);
|
||||
},
|
||||
{
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
|
||||
enabled: isConfigured && openOptions,
|
||||
}
|
||||
);
|
||||
|
||||
return response.data.response.sort((a, b) => {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
},
|
||||
enabled: isConfigured,
|
||||
});
|
||||
const tags = useMemo(() => {
|
||||
return (
|
||||
tagsData?.pages
|
||||
.flatMap((page) => page.tags)
|
||||
.sort((a, b) => a.name.localeCompare(b.name)) ?? []
|
||||
);
|
||||
}, [tagsData]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -427,18 +450,29 @@ const BookmarkForm = () => {
|
||||
onChange={field.onChange}
|
||||
value={[{ name: 'Loading tags...' }]}
|
||||
tags={[{ id: 1, name: 'Loading tags...' }]}
|
||||
hasNextPage={false}
|
||||
isFetchingNextPage={false}
|
||||
/>
|
||||
) : tagsError ? (
|
||||
<TagInput
|
||||
onChange={field.onChange}
|
||||
value={[{ name: 'Not found' }]}
|
||||
tags={[{ id: 1, name: 'Not found' }]}
|
||||
hasNextPage={false}
|
||||
isFetchingNextPage={false}
|
||||
/>
|
||||
) : (
|
||||
<TagInput
|
||||
onChange={field.onChange}
|
||||
value={field.value ?? []}
|
||||
tags={tags}
|
||||
hasNextPage={hasNextPage}
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
onSearchChange={setTagSearch}
|
||||
onReachEnd={() => {
|
||||
if (!hasNextPage || isFetchingNextPage) return;
|
||||
void fetchNextPage();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<FormMessage />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, useState } from 'react';
|
||||
import { FC, UIEvent, useMemo, useState } from 'react';
|
||||
import { Button } from './ui/Button.tsx';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from './ui/Popover.tsx';
|
||||
import { Check, ChevronsUpDown } from 'lucide-react';
|
||||
@@ -8,18 +8,53 @@ import {
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from './ui/Command.tsx';
|
||||
import { cn } from '../lib/utils.ts';
|
||||
import { ResponseTags } from '../lib/actions/tags.ts';
|
||||
|
||||
interface TagInputProps {
|
||||
onChange: (tags: { name: string }[]) => void;
|
||||
value: { name: string; id?: number }[];
|
||||
tags: { id: number; name: string }[] | undefined;
|
||||
tags: Pick<ResponseTags, 'id' | 'name'>[] | undefined;
|
||||
hasNextPage?: boolean;
|
||||
isFetchingNextPage?: boolean;
|
||||
onReachEnd?: () => void;
|
||||
onSearchChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
export const TagInput: FC<TagInputProps> = ({ value, onChange, tags }) => {
|
||||
export const TagInput: FC<TagInputProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
tags,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
onReachEnd,
|
||||
onSearchChange,
|
||||
}) => {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const filteredTags = useMemo(() => {
|
||||
if (!Array.isArray(tags)) return [];
|
||||
|
||||
const normalizedInputValue = inputValue.trim().toLowerCase();
|
||||
|
||||
if (!normalizedInputValue) return tags;
|
||||
|
||||
return tags.filter((tag) =>
|
||||
tag.name.toLowerCase().includes(normalizedInputValue)
|
||||
);
|
||||
}, [inputValue, tags]);
|
||||
|
||||
const handleListScroll = (event: UIEvent<HTMLDivElement>) => {
|
||||
if (!hasNextPage || isFetchingNextPage || !onReachEnd) return;
|
||||
|
||||
const target = event.currentTarget;
|
||||
const reachedBottom =
|
||||
target.scrollTop + target.clientHeight >= target.scrollHeight - 16;
|
||||
|
||||
if (reachedBottom) onReachEnd();
|
||||
};
|
||||
|
||||
function handleAddTag() {
|
||||
if (inputValue && value.some((tagObj) => tagObj.name === inputValue))
|
||||
@@ -27,6 +62,7 @@ export const TagInput: FC<TagInputProps> = ({ value, onChange, tags }) => {
|
||||
if (inputValue) {
|
||||
const newTags = [...value, { name: inputValue }];
|
||||
setInputValue('');
|
||||
onSearchChange?.('');
|
||||
onChange(newTags);
|
||||
}
|
||||
}
|
||||
@@ -53,32 +89,34 @@ export const TagInput: FC<TagInputProps> = ({ value, onChange, tags }) => {
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<div className="min-w-full inset-x-0">
|
||||
<PopoverContent className="min-w-full p-0 overflow-y-auto max-h-[200px]">
|
||||
<Command className="flex-grow min-w-full">
|
||||
<PopoverContent className="min-w-full p-0">
|
||||
<Command className="flex-grow min-w-full" shouldFilter={false}>
|
||||
<CommandInput
|
||||
className="min-w-[280px]"
|
||||
placeholder="Search tag or add tag (Enter)"
|
||||
value={inputValue}
|
||||
onValueChange={setInputValue}
|
||||
onValueChange={(value) => {
|
||||
setInputValue(value);
|
||||
onSearchChange?.(value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleAddTag();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<CommandList
|
||||
className="max-h-[200px]"
|
||||
onScroll={handleListScroll}
|
||||
>
|
||||
<CommandEmpty>No tag found.</CommandEmpty>
|
||||
{Array.isArray(tags) && (
|
||||
<CommandGroup className="w-full">
|
||||
{tags
|
||||
.filter((tag) =>
|
||||
tag.name
|
||||
.toLowerCase()
|
||||
.includes(inputValue.trim().toLowerCase())
|
||||
)
|
||||
.map((tag: { name: string }) => (
|
||||
{filteredTags.map((tag: { name: string }) => (
|
||||
<CommandItem
|
||||
className="w-full"
|
||||
key={tag.name}
|
||||
value={tag.name}
|
||||
onSelect={() => {
|
||||
if (Array.isArray(value)) {
|
||||
if (value.some((v) => v.name === tag.name)) {
|
||||
@@ -102,8 +140,18 @@ export const TagInput: FC<TagInputProps> = ({ value, onChange, tags }) => {
|
||||
{tag.name}
|
||||
</CommandItem>
|
||||
))}
|
||||
{isFetchingNextPage ? (
|
||||
<CommandItem
|
||||
className="w-full"
|
||||
value="Loading more tags..."
|
||||
disabled
|
||||
>
|
||||
Loading more tags...
|
||||
</CommandItem>
|
||||
) : null}
|
||||
</CommandGroup>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
|
||||
interface ResponseTags {
|
||||
export interface ResponseTags {
|
||||
id: number;
|
||||
name: string;
|
||||
ownerId: number;
|
||||
@@ -11,11 +11,192 @@ interface ResponseTags {
|
||||
};
|
||||
}
|
||||
|
||||
export async function getTags(baseUrl: string, apiKey: string) {
|
||||
const url = `${baseUrl}/api/v1/tags`;
|
||||
return await axios.get<{ response: ResponseTags[] }>(url, {
|
||||
type ConfigResponse = {
|
||||
response: {
|
||||
INSTANCE_VERSION?: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
type LegacyTagsResponse = {
|
||||
response:
|
||||
| ResponseTags[]
|
||||
| { tags: ResponseTags[]; nextCursor?: number | null };
|
||||
};
|
||||
|
||||
type PaginatedTagsResponse = {
|
||||
data: {
|
||||
tags: ResponseTags[];
|
||||
nextCursor?: number | null;
|
||||
};
|
||||
};
|
||||
|
||||
const MIN_TAG_PAGINATION_VERSION = '2.14.0';
|
||||
const MIN_TAG_SEARCH_VERSION = '2.14.1';
|
||||
const TAG_SORT_NAME_ASC = 2;
|
||||
const tagFeatureSupportCache = new Map<
|
||||
string,
|
||||
{
|
||||
shouldUsePagination: boolean;
|
||||
shouldUseSearch: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
export type TagsPage = {
|
||||
tags: ResponseTags[];
|
||||
nextCursor: number | null;
|
||||
};
|
||||
|
||||
const normalizeVersion = (version?: string | null) => {
|
||||
if (!version) return null;
|
||||
|
||||
return version
|
||||
.replace(/^v/i, '')
|
||||
.split('-')[0]
|
||||
.split('.')
|
||||
.map((part) => Number(part.replace(/\D/g, '')) || 0);
|
||||
};
|
||||
|
||||
const isAtLeastInstanceVersion = (
|
||||
version?: string | null,
|
||||
minimumVersion?: string | null
|
||||
) => {
|
||||
const normalizedVersion = normalizeVersion(version);
|
||||
const normalizedMinimumVersion = normalizeVersion(minimumVersion);
|
||||
|
||||
if (!normalizedVersion || !normalizedMinimumVersion) return false;
|
||||
|
||||
const length = Math.max(
|
||||
normalizedVersion.length,
|
||||
normalizedMinimumVersion.length
|
||||
);
|
||||
|
||||
for (let index = 0; index < length; index++) {
|
||||
const left = normalizedVersion[index] ?? 0;
|
||||
const right = normalizedMinimumVersion[index] ?? 0;
|
||||
|
||||
if (left > right) return true;
|
||||
if (left < right) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const extractTagsPayload = (
|
||||
data: LegacyTagsResponse | PaginatedTagsResponse
|
||||
): { tags: ResponseTags[]; nextCursor: number | null } => {
|
||||
if (Array.isArray((data as LegacyTagsResponse).response)) {
|
||||
return {
|
||||
tags: (data as LegacyTagsResponse).response as ResponseTags[],
|
||||
nextCursor: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
(data as LegacyTagsResponse).response &&
|
||||
!Array.isArray((data as LegacyTagsResponse).response)
|
||||
) {
|
||||
const response = (data as LegacyTagsResponse).response as {
|
||||
tags: ResponseTags[];
|
||||
nextCursor?: number | null;
|
||||
};
|
||||
|
||||
return {
|
||||
tags: response.tags,
|
||||
nextCursor: response.nextCursor ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
const response = (data as PaginatedTagsResponse).data;
|
||||
|
||||
return {
|
||||
tags: response.tags,
|
||||
nextCursor: response.nextCursor ?? null,
|
||||
};
|
||||
};
|
||||
|
||||
const getInstanceVersion = async (baseUrl: string, apiKey: string) => {
|
||||
try {
|
||||
const response = await axios.get<ConfigResponse>(
|
||||
`${baseUrl}/api/v1/config`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return response.data.response.INSTANCE_VERSION ?? null;
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getTagFeatureSupportCacheKey = (baseUrl: string, apiKey: string) =>
|
||||
`${baseUrl}::${apiKey}`;
|
||||
|
||||
const getTagFeatures = async (baseUrl: string, apiKey: string) => {
|
||||
const cacheKey = getTagFeatureSupportCacheKey(baseUrl, apiKey);
|
||||
const cachedValue = tagFeatureSupportCache.get(cacheKey);
|
||||
|
||||
if (cachedValue !== undefined) return cachedValue;
|
||||
|
||||
const instanceVersion = await getInstanceVersion(baseUrl, apiKey);
|
||||
const nextValue = {
|
||||
shouldUsePagination: isAtLeastInstanceVersion(
|
||||
instanceVersion,
|
||||
MIN_TAG_PAGINATION_VERSION
|
||||
),
|
||||
shouldUseSearch: isAtLeastInstanceVersion(
|
||||
instanceVersion,
|
||||
MIN_TAG_SEARCH_VERSION
|
||||
),
|
||||
};
|
||||
|
||||
tagFeatureSupportCache.set(cacheKey, nextValue);
|
||||
|
||||
return nextValue;
|
||||
};
|
||||
|
||||
export const getShouldUseTagSearch = async (baseUrl: string, apiKey: string) =>
|
||||
(await getTagFeatures(baseUrl, apiKey)).shouldUseSearch;
|
||||
|
||||
export async function getTags(
|
||||
baseUrl: string,
|
||||
apiKey: string,
|
||||
cursor = 0,
|
||||
search = ''
|
||||
): Promise<TagsPage> {
|
||||
const { shouldUsePagination, shouldUseSearch } = await getTagFeatures(
|
||||
baseUrl,
|
||||
apiKey
|
||||
);
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
};
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
const normalizedSearch = search.trim();
|
||||
searchParams.set('sort', String(TAG_SORT_NAME_ASC));
|
||||
|
||||
if (shouldUsePagination) {
|
||||
searchParams.set('cursor', String(cursor));
|
||||
}
|
||||
|
||||
if (shouldUseSearch && normalizedSearch) {
|
||||
searchParams.set('search', normalizedSearch);
|
||||
}
|
||||
|
||||
const initialResponse = await axios.get<
|
||||
LegacyTagsResponse | PaginatedTagsResponse
|
||||
>(`${baseUrl}/api/v1/tags?${searchParams.toString()}`, {
|
||||
headers,
|
||||
});
|
||||
|
||||
const payload = extractTagsPayload(initialResponse.data);
|
||||
|
||||
return {
|
||||
tags: payload.tags,
|
||||
nextCursor: shouldUsePagination ? payload.nextCursor : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,19 +72,58 @@ 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> {
|
||||
@@ -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,8 +342,9 @@ browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
|
||||
}
|
||||
})();
|
||||
|
||||
// Omnibox implementation
|
||||
// Omnibox implementation (not available in Safari)
|
||||
|
||||
if (hasAPI('omnibox.onInputStarted')) {
|
||||
browser.omnibox.onInputStarted.addListener(async () => {
|
||||
const configured = await isConfigured();
|
||||
const description = configured
|
||||
@@ -422,3 +419,4 @@ browser.omnibox.onInputEntered.addListener(
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||