26 Commits

Author SHA1 Message Date
0c12f52569 Release v25.9.18
All checks were successful
CI / Checks (push) Successful in 18m22s
2025-09-18 20:43:05 +02:00
a6f01ba71e BTHLABS-0000: Fix bumping of the workspace 2025-09-18 20:42:49 +02:00
77526b1fae BTHLABS-0000: Allow bumping a single service from top-level tasks. 2025-09-18 20:41:04 +02:00
7c97445155 BTHLABS-0000: Fix a bug that prevented RPC-created saves from processing 2025-09-18 20:41:04 +02:00
a0f1a4ce80 Release v25.9.17
All checks were successful
CI / Checks (push) Successful in 16m16s
2025-09-17 20:38:59 +02:00
80fbbcddf3 BTHLABS-0000: bump-version task
Enough with manual version bumps :D
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-17 20:27:20 +02:00
0ab87e25a4 BTHLABS-0000: Add scope to PWA manifest so share sheet target. 2025-09-17 20:27:20 +02:00
495255206e BTHLABS-57: Pre-auth page in the extension 2025-09-17 20:27:20 +02:00
46254730bd BTHLABS-52: Firefox Desktop Extension 2025-09-17 20:27:08 +02:00
d1e60babf4 BTHLABS-56: _Copy share link_ button in view association page
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-15 06:28:38 +00:00
ab84f685c0 BTHLABS-51: Chrome Web Extension
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-14 06:34:43 +00:00
1a8c4bfebc BTHLABS-0000: eslint.config.js fixes and code cleanup 2025-09-13 09:05:29 +02:00
b15b48f702 BTHLABS-55: Inline create save form
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-13 06:56:44 +00:00
29c732faa0 Release v25.9.12
All checks were successful
CI / Checks (push) Successful in 15m7s
2025-09-11 20:50:18 +02:00
dcebccf947 BTHLABS-50: Safari Web Extension: Reloaded
Turns out, getting this thing out into the wild isn't as simple as I thought :D
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-11 15:57:11 +00:00
67138c7035 BTHLABS-0000: Use absolute URLs in ui.meta.manifest_json 2025-09-09 15:21:45 +02:00
6f848be1ee Release v25.9.8
All checks were successful
CI / Checks (push) Successful in 14m34s
2025-09-08 21:06:53 +02:00
b6d02dbe78 BTHLABS-50: Safari Web extension
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-08 18:11:36 +00:00
ffecf780ee BTHLABS-0000: Enable actions on development branch 2025-08-30 08:42:01 +02:00
fad9d2d26f BTHLABS-0000: Filter ci workflow's triggers. 2025-08-30 08:01:31 +02:00
f68e5b4573 Release v1.0.2
All checks were successful
CI / Checks (push) Successful in 16m8s
2025-08-29 14:15:16 +02:00
db39c38594 BTHLABS-54: Unprocessed save shows Untitled in the card title 2025-08-29 08:23:09 +02:00
38d768a584 BTHLABS-53: Processing task fails for newly created saves 2025-08-29 08:20:53 +02:00
fb39818be3 public -> development 2025-08-20 20:01:12 +00:00
f04c243470 Release v1.0.1
All checks were successful
CI / Checks (push) Successful in 10m26s
2025-08-20 21:57:41 +02:00
f7a4e16d03 BTHLABS-0000: Code cleanup ;) 2025-08-20 21:37:31 +02:00
235 changed files with 9421 additions and 281 deletions

View File

@@ -2,7 +2,13 @@ name: "CI"
on: on:
push: push:
branches:
- "development"
- "public"
pull_request: pull_request:
branches:
- "development"
- "public"
jobs: jobs:
run-checks: run-checks:
@@ -55,14 +61,29 @@ jobs:
push: false push: false
load: true load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/packages:ci-local" tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/packages:ci-local"
- name: "Build `extension-ci` image"
uses: docker/build-push-action@v6
with:
file: "services/extension/Dockerfile"
context: "services/"
target: "ci"
push: false
load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-local"
- name: "Run `backend` checks" - name: "Run `backend` checks"
run: | run: |
set -x set -x
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm backend-ci inv ci docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm backend-ci inv ci
- name: "Run `packages` checks" - name: "Run `packages` checks"
if: always()
run: | run: |
set -x set -x
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm packages-ci inv ci docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm packages-ci inv ci
- name: "Run `extension` checks"
if: always()
run: |
set -x
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm extension-ci inv ci
- name: "Clean up" - name: "Clean up"
if: always() if: always()
run: | run: |

View File

@@ -66,7 +66,7 @@ $ docker run --rm -it \
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME=hotpocket \ -e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME=hotpocket \
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD=hotpocketm4st3r \ -e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD=hotpocketm4st3r \
-p 8000:8000 \ -p 8000:8000 \
docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v1.0.0-01 docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.9.18-01
``` ```
The command above will set up and start the application. The SQLite file will The command above will set up and start the application. The SQLite file will

View File

@@ -1,6 +1,6 @@
services: services:
backend: backend:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v1.0.0-01" image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.9.18-01"
environment: environment:
HOTPOCKET_BACKEND_SECRET_KEY: "thisisntright" HOTPOCKET_BACKEND_SECRET_KEY: "thisisntright"
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME: "hotpocket" HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME: "hotpocket"

View File

@@ -8,7 +8,7 @@ x-backend-environment: &x-backend-environment
services: services:
webapp: webapp:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v1.0.0-01" image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.18-01"
environment: environment:
<<: *x-backend-environment <<: *x-backend-environment
HOTPOCKET_BACKEND_ALLOWED_HOSTS: "app.staging.hotpocket.bthlab.bthlabs.net" HOTPOCKET_BACKEND_ALLOWED_HOSTS: "app.staging.hotpocket.bthlab.bthlabs.net"
@@ -21,7 +21,7 @@ services:
restart: "unless-stopped" restart: "unless-stopped"
admin: admin:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v1.0.0-01" image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.18-01"
environment: environment:
<<: *x-backend-environment <<: *x-backend-environment
HOTPOCKET_BACKEND_APP: "admin" HOTPOCKET_BACKEND_APP: "admin"
@@ -35,7 +35,7 @@ services:
restart: "unless-stopped" restart: "unless-stopped"
celery-worker: celery-worker:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v1.0.0-01" image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.18-01"
command: command:
- "/srv/venv/bin/celery" - "/srv/venv/bin/celery"
- "-A" - "-A"
@@ -57,7 +57,7 @@ services:
restart: "unless-stopped" restart: "unless-stopped"
celery-beat: celery-beat:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v1.0.0-01" image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.18-01"
command: command:
- "/srv/venv/bin/celery" - "/srv/venv/bin/celery"
- "-A" - "-A"

View File

@@ -4,6 +4,7 @@
"targets": [ "targets": [
"backend-management", "backend-management",
"caddy", "caddy",
"extension-management",
"keycloak", "keycloak",
"packages-management", "packages-management",
"postgres", "postgres",
@@ -67,6 +68,28 @@
"type=docker,load=true,push=false" "type=docker,load=true,push=false"
] ]
}, },
"extension-management": {
"context": "services/",
"dockerfile": "extension/Dockerfile",
"tags": [
"docker-hosted.nexus.bthlabs.pl/hotpocket/extension:local"
],
"target": "development",
"output": [
"type=docker,load=true,push=false"
]
},
"extension-ci": {
"context": "services/",
"dockerfile": "extension/Dockerfile",
"tags": [
"docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-local"
],
"target": "ci",
"output": [
"type=docker,load=true,push=false"
]
},
"caddy": { "caddy": {
"context": "services/", "context": "services/",
"dockerfile": "caddy/Dockerfile", "dockerfile": "caddy/Dockerfile",

View File

@@ -13,3 +13,4 @@ services:
include: include:
- path: "./services/backend/docker-compose-ci.yaml" - path: "./services/backend/docker-compose-ci.yaml"
- path: "./services/packages/docker-compose-ci.yaml" - path: "./services/packages/docker-compose-ci.yaml"
- path: "./services/extension/docker-compose-ci.yaml"

View File

@@ -5,6 +5,7 @@ include:
- path: "./docker-compose-cloud.yaml" - path: "./docker-compose-cloud.yaml"
- path: "./services/backend/docker-compose.yaml" - path: "./services/backend/docker-compose.yaml"
- path: "./services/packages/docker-compose.yaml" - path: "./services/packages/docker-compose.yaml"
- path: "./services/extension/docker-compose.yaml"
volumes: {} volumes: {}

View File

@@ -1,6 +0,0 @@
{
"run": {
"echo": true,
"pty": true
}
}

8
invoke.yaml Normal file
View File

@@ -0,0 +1,8 @@
run:
echo: true
pty: true
files_to_version:
- "deployment/aio/docker-compose.yaml"
- "deployment/fullstack/docker-compose.yaml"
- "pyproject.toml"
- "README.md"

3
poetry.lock generated
View File

@@ -9,6 +9,9 @@ python-versions = "^3.12"
files = [] files = []
develop = true develop = true
[package.dependencies]
invoke = "2.2.0"
[package.source] [package.source]
type = "directory" type = "directory"
url = "services/packages/workspace_tools" url = "services/packages/workspace_tools"

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "hotpocket-workspace" name = "hotpocket-workspace"
version = "1.0.0" version = "25.9.18"
description = "HotPocket Workspace" description = "HotPocket Workspace"
authors = ["Tomek Wójcik <contact@bthlabs.pl>"] authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
license = "Apache-2.0" license = "Apache-2.0"

View File

@@ -1,4 +1,5 @@
_tmp/ _tmp/
apple/
backend/node_modules/ backend/node_modules/
backend/ops/metal/ backend/ops/metal/
backend/hotpocket_backend/playground.py backend/hotpocket_backend/playground.py
@@ -7,4 +8,6 @@ backend/hotpocket_backend/settings/docker/
backend/hotpocket_backend/secrets/metal/ backend/hotpocket_backend/secrets/metal/
backend/hotpocket_backend/settings/metal/ backend/hotpocket_backend/settings/metal/
backend/hotpocket_backend/static/ backend/hotpocket_backend/static/
extension/node_modules/
extension/dist/
.envrc* .envrc*

94
services/apple/.gitignore vendored Normal file
View File

@@ -0,0 +1,94 @@
# Xcode
## Build generated
build/
DerivedData/
## Various settings
ExportOptions.plist
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
## We don't want any memgrap's to leak ;)
**.memgraph
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
.build/
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# Accio dependency management
Dependencies/
.accio/
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
fastlane/*.env
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
# Extension stuff
Shared (Extension)/Resources/_locales/
Shared (Extension)/Resources/images/
Shared (Extension)/Resources/background-bundle.js
Shared (Extension)/Resources/content-bundle.js
Shared (Extension)/Resources/manifest.json
Shared (Extension)/Resources/preauth.html
Shared (Extension)/Resources/preauth.js

View File

@@ -0,0 +1,913 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
4CABCAD62E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4CABCAE02E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
4CABCAD72E56F0C900D8A354 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4CABCA922E56F0C800D8A354 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 4CABCAD42E56F0C900D8A354;
remoteInfo = "HotPocket Extension (iOS)";
};
4CABCAE12E56F0C900D8A354 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4CABCA922E56F0C800D8A354 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 4CABCADE2E56F0C900D8A354;
remoteInfo = "HotPocket Extension (macOS)";
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
4CABCB092E56F0C900D8A354 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
4CABCAD62E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
4CABCB132E56F0C900D8A354 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
4CABCAE02E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
4CABCAB02E56F0C900D8A354 /* HotPocket.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HotPocket.app; sourceTree = BUILT_PRODUCTS_DIR; };
4CABCAC62E56F0C900D8A354 /* HotPocket.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HotPocket.app; sourceTree = BUILT_PRODUCTS_DIR; };
4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "HotPocket Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "HotPocket Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
4CABCB042E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (iOS)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
"/Localized: Resources/Main.html",
Assets.xcassets,
"Resources/icon-mac-384.png",
Resources/Script.js,
Resources/Style.css,
ViewController.m,
);
target = 4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */;
};
4CABCB082E56F0C900D8A354 /* Exceptions for "iOS (Extension)" folder in "HotPocket Extension (iOS)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */;
};
4CABCB0D2E56F0C900D8A354 /* Exceptions for "iOS (App)" folder in "HotPocket (iOS)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */;
};
4CABCB0E2E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (macOS)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
"/Localized: Resources/Main.html",
Assets.xcassets,
"Resources/icon-mac-384.png",
Resources/Script.js,
Resources/Style.css,
ViewController.m,
);
target = 4CABCAC52E56F0C900D8A354 /* HotPocket (macOS) */;
};
4CABCB122E56F0C900D8A354 /* Exceptions for "macOS (Extension)" folder in "HotPocket Extension (macOS)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */;
};
4CABCB172E56F0C900D8A354 /* Exceptions for "Shared (Extension)" folder in "HotPocket Extension (iOS)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Resources/_locales,
"Resources/background-bundle.js",
"Resources/content-bundle.js",
Resources/images,
Resources/manifest.json,
Resources/preauth.html,
Resources/preauth.js,
SafariWebExtensionHandler.m,
);
target = 4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */;
};
4CABCB182E56F0C900D8A354 /* Exceptions for "Shared (Extension)" folder in "HotPocket Extension (macOS)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Resources/_locales,
"Resources/background-bundle.js",
"Resources/content-bundle.js",
Resources/images,
Resources/manifest.json,
Resources/preauth.html,
Resources/preauth.js,
SafariWebExtensionHandler.m,
);
target = 4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
4CABCA962E56F0C800D8A354 /* Shared (App) */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4CABCB042E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (iOS)" target */,
4CABCB0E2E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (macOS)" target */,
);
path = "Shared (App)";
sourceTree = "<group>";
};
4CABCAA02E56F0C900D8A354 /* Shared (Extension) */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4CABCB172E56F0C900D8A354 /* Exceptions for "Shared (Extension)" folder in "HotPocket Extension (iOS)" target */,
4CABCB182E56F0C900D8A354 /* Exceptions for "Shared (Extension)" folder in "HotPocket Extension (macOS)" target */,
);
explicitFolders = (
Resources/_locales,
Resources/images,
);
path = "Shared (Extension)";
sourceTree = "<group>";
};
4CABCAB22E56F0C900D8A354 /* iOS (App) */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4CABCB0D2E56F0C900D8A354 /* Exceptions for "iOS (App)" folder in "HotPocket (iOS)" target */,
);
path = "iOS (App)";
sourceTree = "<group>";
};
4CABCAC72E56F0C900D8A354 /* macOS (App) */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = "macOS (App)";
sourceTree = "<group>";
};
4CABCAD92E56F0C900D8A354 /* iOS (Extension) */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4CABCB082E56F0C900D8A354 /* Exceptions for "iOS (Extension)" folder in "HotPocket Extension (iOS)" target */,
);
path = "iOS (Extension)";
sourceTree = "<group>";
};
4CABCAE32E56F0C900D8A354 /* macOS (Extension) */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4CABCB122E56F0C900D8A354 /* Exceptions for "macOS (Extension)" folder in "HotPocket Extension (macOS)" target */,
);
path = "macOS (Extension)";
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
4CABCAAD2E56F0C900D8A354 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCAC32E56F0C900D8A354 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCAD22E56F0C900D8A354 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCADC2E56F0C900D8A354 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
4CABCA912E56F0C800D8A354 = {
isa = PBXGroup;
children = (
4CABCA962E56F0C800D8A354 /* Shared (App) */,
4CABCAA02E56F0C900D8A354 /* Shared (Extension) */,
4CABCAB22E56F0C900D8A354 /* iOS (App) */,
4CABCAC72E56F0C900D8A354 /* macOS (App) */,
4CABCAD92E56F0C900D8A354 /* iOS (Extension) */,
4CABCAE32E56F0C900D8A354 /* macOS (Extension) */,
4CABCAB12E56F0C900D8A354 /* Products */,
);
sourceTree = "<group>";
};
4CABCAB12E56F0C900D8A354 /* Products */ = {
isa = PBXGroup;
children = (
4CABCAB02E56F0C900D8A354 /* HotPocket.app */,
4CABCAC62E56F0C900D8A354 /* HotPocket.app */,
4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */,
4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4CABCB0A2E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket (iOS)" */;
buildPhases = (
4CABCAAC2E56F0C900D8A354 /* Sources */,
4CABCAAD2E56F0C900D8A354 /* Frameworks */,
4CABCAAE2E56F0C900D8A354 /* Resources */,
4CABCB092E56F0C900D8A354 /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
4CABCAD82E56F0C900D8A354 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
4CABCAB22E56F0C900D8A354 /* iOS (App) */,
);
name = "HotPocket (iOS)";
packageProductDependencies = (
);
productName = "HotPocket (iOS)";
productReference = 4CABCAB02E56F0C900D8A354 /* HotPocket.app */;
productType = "com.apple.product-type.application";
};
4CABCAC52E56F0C900D8A354 /* HotPocket (macOS) */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4CABCB142E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket (macOS)" */;
buildPhases = (
4CABCAC22E56F0C900D8A354 /* Sources */,
4CABCAC32E56F0C900D8A354 /* Frameworks */,
4CABCAC42E56F0C900D8A354 /* Resources */,
4CABCB132E56F0C900D8A354 /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
4CABCAE22E56F0C900D8A354 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
4CABCAC72E56F0C900D8A354 /* macOS (App) */,
);
name = "HotPocket (macOS)";
packageProductDependencies = (
);
productName = "HotPocket (macOS)";
productReference = 4CABCAC62E56F0C900D8A354 /* HotPocket.app */;
productType = "com.apple.product-type.application";
};
4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4CABCB052E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket Extension (iOS)" */;
buildPhases = (
4CABCAD12E56F0C900D8A354 /* Sources */,
4CABCAD22E56F0C900D8A354 /* Frameworks */,
4CABCAD32E56F0C900D8A354 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
4CABCAD92E56F0C900D8A354 /* iOS (Extension) */,
);
name = "HotPocket Extension (iOS)";
packageProductDependencies = (
);
productName = "HotPocket Extension (iOS)";
productReference = 4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */;
productType = "com.apple.product-type.app-extension";
};
4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4CABCB0F2E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket Extension (macOS)" */;
buildPhases = (
4CABCADB2E56F0C900D8A354 /* Sources */,
4CABCADC2E56F0C900D8A354 /* Frameworks */,
4CABCADD2E56F0C900D8A354 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
4CABCAE32E56F0C900D8A354 /* macOS (Extension) */,
);
name = "HotPocket Extension (macOS)";
packageProductDependencies = (
);
productName = "HotPocket Extension (macOS)";
productReference = 4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
4CABCA922E56F0C800D8A354 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastUpgradeCheck = 1640;
TargetAttributes = {
4CABCAAF2E56F0C900D8A354 = {
CreatedOnToolsVersion = 16.4;
};
4CABCAC52E56F0C900D8A354 = {
CreatedOnToolsVersion = 16.4;
};
4CABCAD42E56F0C900D8A354 = {
CreatedOnToolsVersion = 16.4;
};
4CABCADE2E56F0C900D8A354 = {
CreatedOnToolsVersion = 16.4;
};
};
};
buildConfigurationList = 4CABCA952E56F0C800D8A354 /* Build configuration list for PBXProject "HotPocket" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 4CABCA912E56F0C800D8A354;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = 4CABCAB12E56F0C900D8A354 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */,
4CABCAC52E56F0C900D8A354 /* HotPocket (macOS) */,
4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */,
4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
4CABCAAE2E56F0C900D8A354 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCAC42E56F0C900D8A354 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCAD32E56F0C900D8A354 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCADD2E56F0C900D8A354 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
4CABCAAC2E56F0C900D8A354 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCAC22E56F0C900D8A354 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCAD12E56F0C900D8A354 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCADB2E56F0C900D8A354 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
4CABCAD82E56F0C900D8A354 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */;
targetProxy = 4CABCAD72E56F0C900D8A354 /* PBXContainerItemProxy */;
};
4CABCAE22E56F0C900D8A354 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */;
targetProxy = 4CABCAE12E56F0C900D8A354 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
4CABCB062E56F0C900D8A354 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091701;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 25.9.17;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
);
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.Extension;
PRODUCT_NAME = "HotPocket Extension";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
4CABCB072E56F0C900D8A354 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091701;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 25.9.17;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
);
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.Extension;
PRODUCT_NAME = "HotPocket Extension";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
4CABCB0B2E56F0C900D8A354 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091701;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (App)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 25.9.17;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
"-framework",
WebKit,
);
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket;
PRODUCT_NAME = HotPocket;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
4CABCB0C2E56F0C900D8A354 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091701;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (App)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 25.9.17;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
"-framework",
WebKit,
);
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket;
PRODUCT_NAME = HotPocket;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
4CABCB102E56F0C900D8A354 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091701;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "macOS (Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 25.9.17;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
);
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.Extension;
PRODUCT_NAME = "HotPocket Extension";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
};
name = Debug;
};
4CABCB112E56F0C900D8A354 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091701;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "macOS (Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 25.9.17;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
);
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.Extension;
PRODUCT_NAME = "HotPocket Extension";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
};
name = Release;
};
4CABCB152E56F0C900D8A354 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091701;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
INFOPLIST_KEY_NSMainStoryboardFile = Main;
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.9.17;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
"-framework",
WebKit,
);
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket;
PRODUCT_NAME = HotPocket;
REGISTER_APP_GROUPS = YES;
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = YES;
};
name = Debug;
};
4CABCB162E56F0C900D8A354 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091701;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
INFOPLIST_KEY_NSMainStoryboardFile = Main;
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.9.17;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
"-framework",
WebKit,
);
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket;
PRODUCT_NAME = HotPocket;
REGISTER_APP_GROUPS = YES;
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = YES;
};
name = Release;
};
4CABCB192E56F0C900D8A354 /* 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;
DEVELOPMENT_TEAM = 648728X64K;
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;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
};
name = Debug;
};
4CABCB1A2E56F0C900D8A354 /* 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";
DEVELOPMENT_TEAM = 648728X64K;
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;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
4CABCA952E56F0C800D8A354 /* Build configuration list for PBXProject "HotPocket" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4CABCB192E56F0C900D8A354 /* Debug */,
4CABCB1A2E56F0C900D8A354 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4CABCB052E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket Extension (iOS)" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4CABCB062E56F0C900D8A354 /* Debug */,
4CABCB072E56F0C900D8A354 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4CABCB0A2E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket (iOS)" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4CABCB0B2E56F0C900D8A354 /* Debug */,
4CABCB0C2E56F0C900D8A354 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4CABCB0F2E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket Extension (macOS)" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4CABCB102E56F0C900D8A354 /* Debug */,
4CABCB112E56F0C900D8A354 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4CABCB142E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket (macOS)" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4CABCB152E56F0C900D8A354 /* Debug */,
4CABCB162E56F0C900D8A354 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 4CABCA922E56F0C800D8A354 /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

3
services/apple/README.md Normal file
View File

@@ -0,0 +1,3 @@
# HotPocket by BTHLabs
This repository contains the _HotPocket Apple Integrations_ project.

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xED",
"green" : "0xBA",
"red" : "0x1C"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,93 @@
{
"images" : [
{
"filename" : "icon-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "icon-1024 1.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "icon-1024 2.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"filename" : "icon-16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "icon-32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "icon-32 1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "icon-64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "icon-mac-1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x29",
"green" : "0x25",
"red" : "0x21"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "icon-large-128.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon-large-128@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon-large-128@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xDD",
"green" : "0x82",
"red" : "0x00"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="../Style.css">
<script src="../Script.js" defer></script>
</head>
<body>
<img src="../icon-mac-384.png" width="128" height="128" alt="HotPocket Icon">
<p class="platform-ios">You can turn on Save to Hotpocket Safari extension in Settings.</p>
<p class="platform-mac state-unknown">You can turn on Save to Hotpocket extension in Safari Extensions preferences.</p>
<p class="platform-mac state-on">Save to Hotpocket extension is currently on. You can turn it off in Safari Extensions preferences.</p>
<p class="platform-mac state-off">Save to Hotpocket extension is currently off. You can turn it on in Safari Extensions preferences.</p>
<button class="platform-mac open-preferences">Quit and Open Safari Extensions Preferences…</button>
</body>
</html>

View File

@@ -0,0 +1,24 @@
function show(platform, enabled, useSettingsInsteadOfPreferences) {
document.body.classList.add(`platform-${platform}`);
if (useSettingsInsteadOfPreferences) {
document.getElementsByClassName('platform-mac state-on')[0].innerText = "Save to Hotpocket extension is currently on. You can turn it off in the Extensions section of Safari Settings.";
document.getElementsByClassName('platform-mac state-off')[0].innerText = "Save to Hotpocket extension is currently off. You can turn it on in the Extensions section of Safari Settings.";
document.getElementsByClassName('platform-mac state-unknown')[0].innerText = "You can turn on Save to Hotpocket extension in the Extensions section of Safari Settings.";
document.getElementsByClassName('platform-mac open-preferences')[0].innerText = "Quit and Open Safari Settings…";
}
if (typeof enabled === "boolean") {
document.body.classList.toggle(`state-on`, enabled);
document.body.classList.toggle(`state-off`, !enabled);
} else {
document.body.classList.remove(`state-on`);
document.body.classList.remove(`state-off`);
}
}
function openPreferences() {
webkit.messageHandlers.controller.postMessage("open-preferences");
}
document.querySelector("button.open-preferences").addEventListener("click", openPreferences);

View File

@@ -0,0 +1,63 @@
* {
-webkit-user-select: none;
-webkit-user-drag: none;
cursor: default;
}
:root {
color-scheme: light dark;
--spacing: 20px;
}
html {
height: 100%;
}
body {
background: #212529;
color: white;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: var(--spacing);
margin: 0 calc(var(--spacing) * 2);
height: 100%;
font: -apple-system-short-body;
text-align: center;
}
body:not(.platform-mac, .platform-ios) :is(.platform-mac, .platform-ios) {
display: none;
}
body.platform-ios .platform-mac {
display: none;
}
body.platform-mac .platform-ios {
display: none;
}
body.platform-ios .platform-mac {
display: none;
}
body:not(.state-on, .state-off) :is(.state-on, .state-off) {
display: none;
}
body.state-on :is(.state-off, .state-unknown) {
display: none;
}
body.state-off :is(.state-on, .state-unknown) {
display: none;
}
button {
font-size: 1em;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,26 @@
//
// ViewController.h
// Shared (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import <TargetConditionals.h>
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
typedef UIViewController PlatformViewController;
#elif TARGET_OS_OSX
#import <Cocoa/Cocoa.h>
typedef NSViewController PlatformViewController;
#endif
@interface ViewController : PlatformViewController
@end

View File

@@ -0,0 +1,76 @@
//
// ViewController.m
// Shared (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import "ViewController.h"
#import <WebKit/WebKit.h>
#if TARGET_OS_OSX
#import <SafariServices/SafariServices.h>
#endif
static NSString * const extensionBundleIdentifier = @"pl.bthlabs.HotPocket.HotPocket.Extension";
@interface ViewController () <WKNavigationDelegate, WKScriptMessageHandler>
@property (nonatomic) IBOutlet WKWebView *webView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_webView.navigationDelegate = self;
#if TARGET_OS_IOS
_webView.scrollView.scrollEnabled = NO;
#endif
[_webView.configuration.userContentController addScriptMessageHandler:self name:@"controller"];
[_webView loadFileURL:[NSBundle.mainBundle URLForResource:@"Main" withExtension:@"html"] allowingReadAccessToURL:NSBundle.mainBundle.resourceURL];
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
#if TARGET_OS_IOS
[webView evaluateJavaScript:@"show('ios')" completionHandler:nil];
#elif TARGET_OS_OSX
[webView evaluateJavaScript:@"show('mac')" completionHandler:nil];
[SFSafariExtensionManager getStateOfSafariExtensionWithIdentifier:extensionBundleIdentifier completionHandler:^(SFSafariExtensionState *state, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!state) {
// Insert code to inform the user something went wrong.
return;
}
NSString *isExtensionEnabledAsString = state.isEnabled ? @"true" : @"false";
if (@available(macOS 13, *))
[webView evaluateJavaScript:[NSString stringWithFormat:@"show('mac', %@, true)", isExtensionEnabledAsString] completionHandler:nil];
else
[webView evaluateJavaScript:[NSString stringWithFormat:@"show('mac', %@, false)", isExtensionEnabledAsString] completionHandler:nil];
});
}];
#endif
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
#if TARGET_OS_OSX
if (![message.body isEqualToString:@"open-preferences"])
return;
[SFSafariApplication showPreferencesForExtensionWithIdentifier:extensionBundleIdentifier completionHandler:^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[NSApp terminate:self];
});
}];
#endif
}
@end

View File

@@ -0,0 +1,12 @@
//
// SafariWebExtensionHandler.h
// Shared (Extension)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import <Foundation/Foundation.h>
@interface SafariWebExtensionHandler : NSObject <NSExtensionRequestHandling>
@end

View File

@@ -0,0 +1,43 @@
//
// SafariWebExtensionHandler.m
// Shared (Extension)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import "SafariWebExtensionHandler.h"
#import <SafariServices/SafariServices.h>
@implementation SafariWebExtensionHandler
- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context {
NSExtensionItem *request = context.inputItems.firstObject;
NSUUID *profile;
if (@available(iOS 17.0, macOS 14.0, *)) {
profile = request.userInfo[SFExtensionProfileKey];
} else {
profile = request.userInfo[@"profile"];
}
id message;
if (@available(iOS 15.0, macOS 11.0, *)) {
message = request.userInfo[SFExtensionMessageKey];
} else {
message = request.userInfo[@"message"];
}
NSLog(@"Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", message, profile.UUIDString ?: @"none");
NSExtensionItem *response = [[NSExtensionItem alloc] init];
if (@available(iOS 15.0, macOS 11.0, *)) {
response.userInfo = @{ SFExtensionMessageKey: @{ @"echo": message } };
} else {
response.userInfo = @{ @"message": @{ @"echo": message } };
}
[context completeRequestReturningItems:@[ response ] completionHandler:nil];
}
@end

View File

@@ -0,0 +1,12 @@
//
// AppDelegate.h
// iOS (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@end

View File

@@ -0,0 +1,21 @@
//
// AppDelegate.m
// iOS (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import "AppDelegate.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
return YES;
}
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}
@end

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="Image references" minToolsVersion="12.0"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6HG-Um-bch">
<rect key="frame" x="131" y="363" width="128" height="128"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<imageReference key="image" image="LargeIcon"/>
</imageView>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" name="BackgroundColor"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LargeIcon" width="128" height="128"/>
<namedColor name="BackgroundColor">
<color red="0.12941176470588237" green="0.14509803921568629" blue="0.16078431372549021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<wkWebView contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RDB-ib-igF">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" name="BackgroundColor"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" name="BackgroundColor"/>
</view>
<connections>
<outlet property="webView" destination="RDB-ib-igF" id="avx-RC-qRB"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<namedColor name="BackgroundColor">
<color red="0.12941176470588237" green="0.14509803921568629" blue="0.16078431372549021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

View File

@@ -0,0 +1,25 @@
<?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>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,14 @@
//
// SceneDelegate.h
// iOS (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import <UIKit/UIKit.h>
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (strong, nonatomic) UIWindow * window;
@end

View File

@@ -0,0 +1,12 @@
//
// SceneDelegate.m
// iOS (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import "SceneDelegate.h"
@implementation SceneDelegate
@end

View File

@@ -0,0 +1,18 @@
//
// main.m
// iOS (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
NSString *appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

View File

@@ -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>SafariWebExtensionHandler</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,5 @@
run:
echo: true
pty: true
files_to_version:
- "HotPocket.xcodeproj/project.pbxproj"

View File

@@ -0,0 +1,12 @@
//
// AppDelegate.h
// macOS (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end

View File

@@ -0,0 +1,20 @@
//
// AppDelegate.m
// macOS (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import "AppDelegate.h"
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
// Override point for customization after application launch.
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return YES;
}
@end

View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23727"/>
<plugIn identifier="com.apple.WebKit2IBPlugin" version="23727"/>
<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="HotPocket" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="HotPocket" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About HotPocket" 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 HotPocket" 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 HotPocket" 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="HotPocket 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"/>
<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="HotPocket" 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" sceneMemberID="viewController">
<view key="view" appearanceType="darkAqua" 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>

View File

@@ -0,0 +1,12 @@
<?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>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,15 @@
//
// main.m
// macOS (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
return NSApplicationMain(argc, argv);
}

View File

@@ -0,0 +1,10 @@
<?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>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

View File

@@ -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>SafariWebExtensionHandler</string>
</dict>
</dict>
</plist>

511
services/apple/poetry.lock generated Normal file
View File

@@ -0,0 +1,511 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "asttokens"
version = "3.0.0"
description = "Annotate AST trees with source code positions"
optional = false
python-versions = ">=3.8"
files = [
{file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"},
{file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"},
]
[package.extras]
astroid = ["astroid (>=2,<4)"]
test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"]
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "decorator"
version = "5.2.1"
description = "Decorators for Humans"
optional = false
python-versions = ">=3.8"
files = [
{file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"},
{file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"},
]
[[package]]
name = "executing"
version = "2.2.1"
description = "Get the currently executing AST node of a frame, and other information"
optional = false
python-versions = ">=3.8"
files = [
{file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"},
{file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"},
]
[package.extras]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
[[package]]
name = "factory-boy"
version = "3.3.3"
description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby."
optional = false
python-versions = ">=3.8"
files = [
{file = "factory_boy-3.3.3-py2.py3-none-any.whl", hash = "sha256:1c39e3289f7e667c4285433f305f8d506efc2fe9c73aaea4151ebd5cdea394fc"},
{file = "factory_boy-3.3.3.tar.gz", hash = "sha256:866862d226128dfac7f2b4160287e899daf54f2612778327dd03d0e2cb1e3d03"},
]
[package.dependencies]
Faker = ">=0.7.0"
[package.extras]
dev = ["Django", "Pillow", "SQLAlchemy", "coverage", "flake8", "isort", "mongoengine", "mongomock", "mypy", "tox", "wheel (>=0.32.0)", "zest.releaser[recommended]"]
doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"]
[[package]]
name = "faker"
version = "37.6.0"
description = "Faker is a Python package that generates fake data for you."
optional = false
python-versions = ">=3.9"
files = [
{file = "faker-37.6.0-py3-none-any.whl", hash = "sha256:3c5209b23d7049d596a51db5d76403a0ccfea6fc294ffa2ecfef6a8843b1e6a7"},
{file = "faker-37.6.0.tar.gz", hash = "sha256:0f8cc34f30095184adf87c3c24c45b38b33ad81c35ef6eb0a3118f301143012c"},
]
[package.dependencies]
tzdata = "*"
[[package]]
name = "flake8"
version = "7.3.0"
description = "the modular source code checker: pep8 pyflakes and co"
optional = false
python-versions = ">=3.9"
files = [
{file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"},
{file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"},
]
[package.dependencies]
mccabe = ">=0.7.0,<0.8.0"
pycodestyle = ">=2.14.0,<2.15.0"
pyflakes = ">=3.4.0,<3.5.0"
[[package]]
name = "flake8-commas"
version = "4.0.0"
description = "Flake8 lint for trailing commas."
optional = false
python-versions = ">=3.8"
files = [
{file = "flake8_commas-4.0.0-py3-none-any.whl", hash = "sha256:cad476d71ba72e8b941a8508d5b9ffb6b03e50f7102982474f085ad0d674b685"},
{file = "flake8_commas-4.0.0.tar.gz", hash = "sha256:a68834b42a9a31c94ca790efe557a932c0eae21a3479c6b9a23c4dc077e3ea96"},
]
[package.dependencies]
flake8 = ">=5"
[[package]]
name = "hotpocket-workspace-tools"
version = "1.0.0.dev0"
description = "HotPocket Workspace Tools"
optional = false
python-versions = "^3.12"
files = []
develop = true
[package.dependencies]
invoke = "2.2.0"
[package.source]
type = "directory"
url = "../packages/workspace_tools"
[[package]]
name = "invoke"
version = "2.2.0"
description = "Pythonic task execution"
optional = false
python-versions = ">=3.6"
files = [
{file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"},
{file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"},
]
[[package]]
name = "ipdb"
version = "0.13.13"
description = "IPython-enabled pdb"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
{file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"},
{file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"},
]
[package.dependencies]
decorator = {version = "*", markers = "python_version >= \"3.11\""}
ipython = {version = ">=7.31.1", markers = "python_version >= \"3.11\""}
[[package]]
name = "ipython"
version = "9.3.0"
description = "IPython: Productive Interactive Computing"
optional = false
python-versions = ">=3.11"
files = [
{file = "ipython-9.3.0-py3-none-any.whl", hash = "sha256:1a0b6dd9221a1f5dddf725b57ac0cb6fddc7b5f470576231ae9162b9b3455a04"},
{file = "ipython-9.3.0.tar.gz", hash = "sha256:79eb896f9f23f50ad16c3bc205f686f6e030ad246cc309c6279a242b14afe9d8"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
decorator = "*"
ipython-pygments-lexers = "*"
jedi = ">=0.16"
matplotlib-inline = "*"
pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""}
prompt_toolkit = ">=3.0.41,<3.1.0"
pygments = ">=2.4.0"
stack_data = "*"
traitlets = ">=5.13.0"
[package.extras]
all = ["ipython[doc,matplotlib,test,test-extra]"]
black = ["black"]
doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinx_toml (==0.0.4)", "typing_extensions"]
matplotlib = ["matplotlib"]
test = ["packaging", "pytest", "pytest-asyncio (<0.22)", "testpath"]
test-extra = ["curio", "ipykernel", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "nbclient", "nbformat", "numpy (>=1.23)", "pandas", "trio"]
[[package]]
name = "ipython-pygments-lexers"
version = "1.1.1"
description = "Defines a variety of Pygments lexers for highlighting IPython code."
optional = false
python-versions = ">=3.8"
files = [
{file = "ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c"},
{file = "ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81"},
]
[package.dependencies]
pygments = "*"
[[package]]
name = "isort"
version = "6.0.1"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.9.0"
files = [
{file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"},
{file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"},
]
[package.extras]
colors = ["colorama"]
plugins = ["setuptools"]
[[package]]
name = "jedi"
version = "0.19.2"
description = "An autocompletion tool for Python that can be used for text editors."
optional = false
python-versions = ">=3.6"
files = [
{file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"},
{file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"},
]
[package.dependencies]
parso = ">=0.8.4,<0.9.0"
[package.extras]
docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"]
[[package]]
name = "matplotlib-inline"
version = "0.1.7"
description = "Inline Matplotlib backend for Jupyter"
optional = false
python-versions = ">=3.8"
files = [
{file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"},
{file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"},
]
[package.dependencies]
traitlets = "*"
[[package]]
name = "mccabe"
version = "0.7.0"
description = "McCabe checker, plugin for flake8"
optional = false
python-versions = ">=3.6"
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
[[package]]
name = "mypy"
version = "1.16.1"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.9"
files = [
{file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"},
{file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"},
{file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"},
{file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"},
{file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"},
{file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"},
{file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"},
{file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"},
{file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"},
{file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"},
{file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"},
{file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"},
{file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"},
{file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"},
{file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"},
{file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"},
{file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"},
{file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"},
{file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"},
{file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"},
{file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"},
{file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"},
{file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"},
{file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"},
{file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"},
{file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"},
{file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"},
{file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"},
{file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"},
{file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"},
{file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"},
{file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"},
]
[package.dependencies]
mypy_extensions = ">=1.0.0"
pathspec = ">=0.9.0"
typing_extensions = ">=4.6.0"
[package.extras]
dmypy = ["psutil (>=4.0)"]
faster-cache = ["orjson"]
install-types = ["pip"]
mypyc = ["setuptools (>=50)"]
reports = ["lxml"]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.8"
files = [
{file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
{file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
]
[[package]]
name = "parso"
version = "0.8.5"
description = "A Python Parser"
optional = false
python-versions = ">=3.6"
files = [
{file = "parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887"},
{file = "parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a"},
]
[package.extras]
qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
testing = ["docopt", "pytest"]
[[package]]
name = "pathspec"
version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
name = "pexpect"
version = "4.9.0"
description = "Pexpect allows easy control of interactive console applications."
optional = false
python-versions = "*"
files = [
{file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
{file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
]
[package.dependencies]
ptyprocess = ">=0.5"
[[package]]
name = "prompt-toolkit"
version = "3.0.52"
description = "Library for building powerful interactive command lines in Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"},
{file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"},
]
[package.dependencies]
wcwidth = "*"
[[package]]
name = "ptyprocess"
version = "0.7.0"
description = "Run a subprocess in a pseudo terminal"
optional = false
python-versions = "*"
files = [
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
]
[[package]]
name = "pure-eval"
version = "0.2.3"
description = "Safely evaluate AST nodes without side effects"
optional = false
python-versions = "*"
files = [
{file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"},
{file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"},
]
[package.extras]
tests = ["pytest"]
[[package]]
name = "pycodestyle"
version = "2.14.0"
description = "Python style guide checker"
optional = false
python-versions = ">=3.9"
files = [
{file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"},
{file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"},
]
[[package]]
name = "pyflakes"
version = "3.4.0"
description = "passive checker of Python programs"
optional = false
python-versions = ">=3.9"
files = [
{file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"},
{file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"},
]
[[package]]
name = "pygments"
version = "2.19.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
files = [
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "stack-data"
version = "0.6.3"
description = "Extract data from python stack frames and tracebacks for informative displays"
optional = false
python-versions = "*"
files = [
{file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
{file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
]
[package.dependencies]
asttokens = ">=2.1.0"
executing = ">=1.2.0"
pure-eval = "*"
[package.extras]
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
[[package]]
name = "traitlets"
version = "5.14.3"
description = "Traitlets Python configuration system"
optional = false
python-versions = ">=3.8"
files = [
{file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"},
{file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"},
]
[package.extras]
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
[[package]]
name = "typing-extensions"
version = "4.15.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
files = [
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
]
[[package]]
name = "tzdata"
version = "2025.2"
description = "Provider of IANA time zone data"
optional = false
python-versions = ">=2"
files = [
{file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"},
{file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"},
]
[[package]]
name = "wcwidth"
version = "0.2.13"
description = "Measures the displayed width of unicode strings in a terminal"
optional = false
python-versions = "*"
files = [
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "1c74b6d5928a0b1bde41962f4233af2eba2242324c3155b21093b2f46baf5cd0"

View File

@@ -0,0 +1,26 @@
[tool.poetry]
name = "hotpocket-apple"
version = "25.9.12"
description = "HotPocket Apple Integrations"
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
license = "Apache-2.0"
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = "^3.12"
[tool.poetry.group.dev.dependencies]
factory-boy = "3.3.3"
flake8 = "7.3.0"
flake8-commas = "4.0.0"
hotpocket-workspace-tools = {path = "../packages/workspace_tools", develop = true}
invoke = "2.2.0"
ipdb = "0.13.13"
ipython = "9.3.0"
isort = "6.0.1"
mypy = "1.16.1"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

16
services/apple/setup.cfg Normal file
View File

@@ -0,0 +1,16 @@
[flake8]
extend-exclude =
node_modules/**/*.py
ignore = E131,W503,W504
max-line-length = 119
hang-closing = False
[isort]
known_first_party=hotpocket_backend,hotpocket_backend_testing,hotpocket_common,hotpocket_soa,hotpocket_testing,hotpocket_workspace_tools
multi_line_output=3
include_trailing_comma=true
force_sort_within_sections=true
line_length=80
use_parentheses=true
combine_as_imports=true
star_first=true

124
services/apple/tasks.py Normal file
View File

@@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
# type: ignore
from __future__ import annotations
import re
from invoke import Context, task
from invoke.exceptions import UnexpectedExit
from hotpocket_workspace_tools import get_workspace_mode
import hotpocket_workspace_tools.tasks
WORKSPACE_MODE = get_workspace_mode()
@task
def clean(ctx: Context):
ctx.run('rm -rf DerivedData/')
@task
def test(ctx: Context):
print('NOOP')
@task
def flake8(ctx: Context):
ctx.run('flake8')
@task
def isort(ctx, check=False, diff=False):
command_parts = [
'isort',
]
if check is True:
command_parts.append('--check')
if diff is True:
command_parts.append('--diff')
command_parts.append('.')
ctx.run(' '.join(command_parts))
@task
def eslint(ctx: Context):
print('NOOP')
@task
def lint(ctx: Context):
ihazsuccess = True
try:
flake8(ctx)
except UnexpectedExit:
ihazsuccess = False
try:
isort(ctx, check=True)
except UnexpectedExit:
ihazsuccess = False
if ihazsuccess is False:
raise RuntimeError('FIAL')
@task
def typecheck(ctx: Context):
ctx.run('mypy .')
@task
def django_shell(ctx: Context):
raise NotImplementedError()
@task
def ci(ctx: Context):
ihazsuccess = True
ci_tasks = [test, lint, typecheck]
for ci_task in ci_tasks:
try:
ci_task(ctx)
except UnexpectedExit:
ihazsuccess = False
if ihazsuccess is False:
raise RuntimeError('FIAL')
@task
def setup(ctx: Context):
print('NOOP')
@task
def start_web(ctx: Context):
raise NotImplementedError()
@task
def bump_version(ctx: Context, next_version: str, build: str | None = None):
hotpocket_workspace_tools.tasks.utils.bump_version(
ctx, next_version, build=build,
)
pbxproj_content = None
with open('HotPocket.xcodeproj/project.pbxproj', 'r', encoding='utf-8') as pbxproj_content_f:
pbxproj_content = pbxproj_content_f.read()
pbxproj_content = re.sub(
r'CURRENT_PROJECT_VERSION = [0-9]{10};',
f'CURRENT_PROJECT_VERSION = {build};',
pbxproj_content,
flags=re.MULTILINE,
)
with open('HotPocket.xcodeproj/project.pbxproj', 'w', encoding='utf-8') as pbxproj_content_f:
pbxproj_content_f.write(pbxproj_content)

View File

@@ -7,7 +7,7 @@ export default defineConfig([
{ {
files: [ files: [
'eslint.config.js', 'eslint.config.js',
'hotpocket_backend/apps/ui/static/ui/js/hotpocket.*.js', 'hotpocket_backend/apps/ui/static/ui/js/hotpocket-backend.*.js',
], ],
plugins: { plugins: {
js, js,
@@ -64,4 +64,9 @@ export default defineConfig([
'no-invalid-this': 'off', 'no-invalid-this': 'off',
}, },
}, },
{
ignores: [
'hotpocket_backend/static/**/*.js',
],
},
]); ]);

View File

@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import annotations from __future__ import annotations
version = '1.0.0' version = '25.9.18'

View File

@@ -1 +1,2 @@
from .access_token import AccessToken # noqa: F401
from .account import AccountAdmin # noqa: F401 from .account import AccountAdmin # noqa: F401

View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from django.contrib import admin
from hotpocket_backend.apps.accounts.models import AccessToken
class AccessTokenAdmin(admin.ModelAdmin):
list_display = ('pk', 'account_uuid', 'origin', 'created_at', 'is_active')
search_fields = ('pk', 'account_uuid', 'key', 'origin')
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
admin.site.register(AccessToken, AccessTokenAdmin)

View File

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import logging
import typing
from django.contrib.auth.backends import ModelBackend, UserModel
from django.http import HttpRequest
from hotpocket_backend.apps.accounts.models import AccessToken, Account
LOGGER = logging.getLogger(__name__)
class AccessTokenBackend(ModelBackend):
def authenticate(self,
request: HttpRequest,
access_token: AccessToken | None,
) -> Account | None:
if not access_token:
return None
try:
user = UserModel.objects.get(pk=access_token.account_uuid)
except UserModel.DoesNotExist as exception:
LOGGER.error(
'Unhandled exception in AccessToken auth: %s',
exception,
exc_info=exception,
)
if self.user_can_authenticate(user) is False:
return None
request.access_token = access_token
return typing.cast(Account, user)

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import logging
from django.contrib import auth
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpRequest
from django.utils.deprecation import MiddlewareMixin
from hotpocket_backend.apps.accounts.models import AccessToken, Account
LOGGER = logging.getLogger(__name__)
class AccessTokenMiddleware(MiddlewareMixin):
def process_request(self, request: HttpRequest):
if not hasattr(request, 'user'):
raise ImproperlyConfigured('No `AuthenticationMiddleware`?')
authorization_header = request.headers.get('Authorization', None)
if authorization_header is None:
return
try:
scheme, authorization = authorization_header.split(' ', maxsplit=1)
assert scheme == 'Bearer', (
f'Unsupported authorization scheme: `{scheme}`'
)
access_token = AccessToken.active_objects.get(key=authorization)
except (ValueError, AssertionError, AccessToken.DoesNotExist, Account.DoesNotExist) as exception:
LOGGER.error(
'Unhandled exception in AccessToken middleware: %s',
exception,
exc_info=exception,
)
return
account = auth.authenticate(request, access_token=access_token)
if account:
request.user = account

View File

@@ -0,0 +1,31 @@
# Generated by Django 5.2.3 on 2025-09-04 18:50
import uuid6
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_alter_account_settings_and_more'),
]
operations = [
migrations.CreateModel(
name='AccessToken',
fields=[
('id', models.UUIDField(default=uuid6.uuid7, editable=False, primary_key=True, serialize=False)),
('account_uuid', models.UUIDField(db_index=True, default=None)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('deleted_at', models.DateTimeField(blank=True, db_index=True, default=None, editable=False, null=True)),
('key', models.CharField(db_index=True, default=None, editable=False, max_length=128, unique=True)),
('origin', models.CharField(db_index=True, default=None)),
('meta', models.JSONField(blank=True, default=dict, null=True)),
],
options={
'verbose_name': 'Access Token',
'verbose_name_plural': 'Access Tokens',
},
),
]

View File

@@ -1 +1,2 @@
from .access_token import AccessToken # noqa: F401
from .account import Account # noqa: F401 from .account import Account # noqa: F401

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from django.db import models
from django.utils.translation import gettext_lazy as _
from hotpocket_backend.apps.core.models import Model
class ActiveAccessTokensManager(models.Manager):
def get_queryset(self) -> models.QuerySet[AccessToken]:
return super().get_queryset().filter(
deleted_at__isnull=True,
)
class AccessToken(Model):
key = models.CharField(
blank=False,
default=None,
null=False,
max_length=128,
db_index=True,
unique=True,
editable=False,
)
origin = models.CharField(
blank=False, default=None, null=False, db_index=True,
)
meta = models.JSONField(blank=True, default=dict, null=True)
objects = models.Manager()
active_objects = ActiveAccessTokensManager()
class Meta:
verbose_name = _('Access Token')
verbose_name_plural = _('Access Tokens')
def __str__(self) -> str:
return f'<AccessToken pk={self.pk} account_uuid={self.account_uuid}>'

View File

@@ -0,0 +1 @@
from .access_tokens import AccessTokensService # noqa: F401

View File

@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import hashlib
import hmac
import logging
import uuid
from django.db import models
import uuid6
from hotpocket_backend.apps.accounts.models import AccessToken
from hotpocket_backend.apps.core.conf import settings
from hotpocket_soa.dto.accounts import (
AccessTokenMetaUpdateIn,
AccessTokensQuery,
)
LOGGER = logging.getLogger(__name__)
class AccessTokensService:
class AccessTokensServiceError(Exception):
pass
class AccessTokenNotFound(AccessTokensServiceError):
pass
def create(self,
*,
account_uuid: uuid.UUID,
origin: str,
meta: dict,
) -> AccessToken:
pk = uuid6.uuid7()
key = hmac.new(
settings.SECRET_KEY.encode('ascii'),
msg=pk.bytes,
digestmod=hashlib.sha256,
)
return AccessToken.objects.create(
pk=pk,
account_uuid=account_uuid,
key=key.hexdigest(),
origin=origin,
meta=meta,
)
def get(self, *, pk: uuid.UUID) -> AccessToken:
try:
query_set = AccessToken.active_objects
return query_set.get(pk=pk)
except AccessToken.DoesNotExist as exception:
raise self.AccessTokenNotFound(
f'Access Token not found: pk=`{pk}`',
) from exception
def get_by_key(self, *, key: str) -> AccessToken:
try:
query_set = AccessToken.active_objects
return query_set.get(key=key)
except AccessToken.DoesNotExist as exception:
raise self.AccessTokenNotFound(
f'Access Token not found: key=`{key}`',
) from exception
def search(self,
*,
query: AccessTokensQuery,
offset: int = 0,
limit: int = 10,
order_by: str = '-pk',
) -> models.QuerySet[AccessToken]:
filters = [
models.Q(account_uuid=query.account_uuid),
]
if query.before is not None:
filters.append(models.Q(pk__lt=query.before))
result = AccessToken.active_objects.\
filter(*filters).\
order_by(order_by)
return result[offset:offset + limit]
def delete(self, *, pk: uuid.UUID) -> bool:
access_token = self.get(pk=pk)
access_token.soft_delete()
return True
def update_meta(self,
*,
pk: uuid.UUID,
update: AccessTokenMetaUpdateIn,
) -> AccessToken:
access_token = AccessToken.active_objects.get(pk=pk)
next_meta = {
**(access_token.meta or {}),
}
if update.version is not None:
next_meta['version'] = update.version
if update.platform is not None:
next_meta['platform'] = update.platform
access_token.meta = next_meta
access_token.save()
access_token.refresh_from_db()
return access_token

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import typing
from bthlabs_jsonrpc_django import (
DjangoExecutor,
DjangoJSONRPCSerializer,
JSONRPCView as BaseJSONRPCView,
)
from django.core.exceptions import ValidationError
import uuid6
class JSONRPCSerializer(DjangoJSONRPCSerializer):
STRING_COERCIBLE_TYPES: typing.Any = (
*DjangoJSONRPCSerializer.STRING_COERCIBLE_TYPES,
uuid6.UUID,
)
def serialize_value(self, value: typing.Any) -> typing.Any:
if isinstance(value, ValidationError):
result: typing.Any = None
if hasattr(value, 'error_dict') is True:
result = {}
for field, errors in value.error_dict.items():
result[field] = [
error.code
for error
in errors
]
elif hasattr(value, 'error_list') is True:
result = [
error.code
for error in value.error_list
]
else:
result = value.code
return self.serialize_value(result)
return super().serialize_value(value)
class Executor(DjangoExecutor):
serializer = JSONRPCSerializer
class JSONRPCView(BaseJSONRPCView):
executor = Executor

View File

@@ -3,6 +3,8 @@ from __future__ import annotations
import enum import enum
from django.utils.translation import gettext_lazy as _
class MessageLevelAlertClass(enum.Enum): class MessageLevelAlertClass(enum.Enum):
debug = 'alert-secondary' debug = 'alert-secondary'
@@ -15,3 +17,9 @@ class MessageLevelAlertClass(enum.Enum):
class StarUnstarAssociationViewMode(enum.Enum): class StarUnstarAssociationViewMode(enum.Enum):
STAR = 'STAR' STAR = 'STAR'
UNSTAR = 'UNSTAR' UNSTAR = 'UNSTAR'
class UIAccessTokenOriginApp(enum.Enum):
SAFARI_WEB_EXTENSION = _('Safari Web Extension')
CHROME_EXTENSION = _('Chrome Extension')
FIREFOX_EXTENSION = _('Firefox Extension')

View File

@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from .base import BrowseParams as BaseBrowseParams
class AppsBrowseParams(BaseBrowseParams):
pass

View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from crispy_forms.layout import Submit
from django import forms
from django.utils.translation import gettext_lazy as _
from hotpocket_backend.apps.ui.forms.base import ConfirmationMixin, Form
class AppForm(Form):
pass
class ConfirmationForm(ConfirmationMixin, AppForm):
origin_app = forms.CharField(
label=_('App'),
required=False,
disabled=True,
show_hidden_initial=True,
)
platform = forms.CharField(
label=_('Platform'),
required=False,
disabled=True,
show_hidden_initial=True,
)
version = forms.CharField(
label=_('Version'),
required=False,
disabled=True,
show_hidden_initial=True,
)
def get_layout_fields(self) -> list[str]:
return [
'canhazconfirm',
'origin_app',
'platform',
'version',
]
class DeleteForm(ConfirmationForm):
def get_submit_button(self) -> Submit:
return Submit('submit', _('Delete'), css_class='btn btn-danger')

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit
from django.contrib.auth.forms import (
AuthenticationForm as BaseAuthenticationForm,
)
from django.utils.translation import gettext_lazy as _
class LoginForm(BaseAuthenticationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.attrs = {
'id': self.__class__.__name__,
'novalidate': '',
}
self.helper.layout = Layout(
'username',
'password',
FormActions(
Submit('submit', _('Log in'), css_class='btn btn-primary'),
template='ui/ui/forms/formactions.html',
css_class='mb-0',
),
)

View File

@@ -6,32 +6,11 @@ from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit from crispy_forms.layout import Layout, Submit
from django import forms from django import forms
from django.contrib.auth.forms import ( from django.contrib.auth.forms import (
AuthenticationForm as BaseAuthenticationForm,
PasswordChangeForm as BasePasswordChangeForm, PasswordChangeForm as BasePasswordChangeForm,
) )
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .base import Form from hotpocket_backend.apps.ui.forms.base import Form
class LoginForm(BaseAuthenticationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.attrs = {
'id': self.__class__.__name__,
'novalidate': '',
}
self.helper.layout = Layout(
'username',
'password',
FormActions(
Submit('submit', _('Log in'), css_class='btn btn-primary'),
template='ui/ui/forms/formactions.html',
css_class='mb-0',
),
)
class ProfileForm(Form): class ProfileForm(Form):
@@ -131,17 +110,17 @@ class PasswordForm(BasePasswordChangeForm):
class FederatedPasswordForm(PasswordForm): class FederatedPasswordForm(PasswordForm):
current_password = forms.CharField( old_password = forms.CharField(
label=_('Old password'), label=_('Old password'),
disabled=True, disabled=True,
required=False, required=False,
) )
new_password = forms.CharField( new_password1 = forms.CharField(
label=_('New password'), label=_('New password'),
disabled=True, disabled=True,
required=False, required=False,
) )
new_password_again = forms.CharField( new_password2 = forms.CharField(
label=_('New password confirmation'), label=_('New password confirmation'),
disabled=True, disabled=True,
required=False, required=False,

View File

@@ -5,19 +5,14 @@ from crispy_forms.layout import Submit
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .base import Form from .base import ConfirmationMixin, Form
class AssociationForm(Form): class AssociationForm(Form):
pass pass
class ConfirmationForm(AssociationForm): class ConfirmationForm(ConfirmationMixin, AssociationForm):
canhazconfirm = forms.CharField(
label='',
required=True,
widget=forms.HiddenInput,
)
title = forms.CharField( title = forms.CharField(
label=_('Title'), label=_('Title'),
required=False, required=False,

View File

@@ -61,3 +61,11 @@ class Form(forms.Form):
template=self.get_form_actions_template(), template=self.get_form_actions_template(),
), ),
) )
class ConfirmationMixin(forms.Form):
canhazconfirm = forms.CharField(
label='',
required=True,
widget=forms.HiddenInput,
)

View File

@@ -0,0 +1,2 @@
from . import accounts # noqa: F401
from . import saves # noqa: F401

View File

@@ -0,0 +1,2 @@
from . import access_tokens # noqa: F401
from . import auth # noqa: F401

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import logging
from bthlabs_jsonrpc_core import register_method
from django import db
from django.http import HttpRequest
from hotpocket_soa.services import AccessTokensService
LOGGER = logging.getLogger(__name__)
@register_method('accounts.access_tokens.create')
def create(request: HttpRequest,
auth_key: str,
meta: dict,
) -> str:
with db.transaction.atomic():
try:
assert 'extension_auth_key' in request.session, 'Auth key missing'
assert request.session['extension_auth_key'] == auth_key, (
'Auth key mismatch'
)
except AssertionError as exception:
LOGGER.error(
'Unable to issue access token: %s',
exception,
exc_info=exception,
)
raise
access_token = AccessTokensService().create(
account_uuid=request.user.pk,
origin=request.META['HTTP_ORIGIN'],
meta=meta,
)
request.session.pop('extension_auth_key')
request.session.save()
return access_token.key

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import logging
from bthlabs_jsonrpc_core import register_method
from django import db
from django.http import HttpRequest
from hotpocket_soa.dto.accounts import AccessTokenMetaUpdateIn
from hotpocket_soa.services import AccessTokensService
LOGGER = logging.getLogger(__name__)
@register_method('accounts.auth.check')
def check(request: HttpRequest) -> bool:
return request.user.is_anonymous is False
@register_method('accounts.auth.check_access_token')
def check_access_token(request: HttpRequest,
access_token: str,
meta: dict | None = None,
) -> bool:
result = True
try:
access_tokens_service = AccessTokensService()
with db.transaction.atomic():
access_token_object = access_tokens_service.get_by_key(
account_uuid=request.user.pk,
key=access_token,
)
meta_update = AccessTokenMetaUpdateIn.model_validate(
(meta or {}),
)
_ = access_tokens_service.update_meta(
access_token=access_token_object,
update=meta_update,
)
except AccessTokensService.AccessTokenNotFound as exception:
LOGGER.error(
'Access Token not found: account_uuid=`%s` key=`%s`',
request.user.pk,
access_token,
exc_info=exception,
)
result = False
except AccessTokensService.AccessTokenAccessDenied as exception:
LOGGER.error(
'Access Token access denied: account_uuid=`%s` key=`%s`',
request.user.pk,
access_token,
exc_info=exception,
)
result = False
return result

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from bthlabs_jsonrpc_core import register_method
from django.http import HttpRequest
from hotpocket_backend.apps.ui.services.workflows import CreateSaveWorkflow
from hotpocket_soa.dto.associations import AssociationOut
@register_method(method='saves.create')
def create(request: HttpRequest, url: str) -> AssociationOut:
association = CreateSaveWorkflow().run_rpc(
request=request,
account=request.user,
url=url,
)
return association

View File

@@ -1,3 +1,4 @@
from .access_tokens import UIAccessTokensService # noqa: F401
from .associations import UIAssociationsService # noqa: F401 from .associations import UIAssociationsService # noqa: F401
from .imports import UIImportsService # noqa: F401 from .imports import UIImportsService # noqa: F401
from .saves import UISavesService # noqa: F401 from .saves import UISavesService # noqa: F401

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import logging
import uuid
from django.core.exceptions import PermissionDenied
from django.http import Http404
from hotpocket_soa.dto.accounts import AccessTokenOut
from hotpocket_soa.services import AccessTokensService
LOGGER = logging.getLogger(__name__)
class UIAccessTokensService:
def __init__(self):
self.access_tokens_service = AccessTokensService()
def get_or_404(self,
*,
account_uuid: uuid.UUID,
pk: uuid.UUID,
) -> AccessTokenOut:
try:
return AccessTokensService().get(
account_uuid=account_uuid,
pk=pk,
)
except AccessTokensService.AccessTokenNotFound as exception:
LOGGER.error(
'Access Token not found: account_uuid=`%s` pk=`%s`',
account_uuid,
pk,
exc_info=exception,
)
raise Http404()
except AccessTokensService.AccessTokenAccessDenied as exception:
LOGGER.error(
'Access Token access denied: account_uuid=`%s` pk=`%s`',
account_uuid,
pk,
exc_info=exception,
)
raise PermissionDenied()

View File

@@ -1,19 +1,43 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import annotations from __future__ import annotations
from bthlabs_jsonrpc_core import JSONRPCInternalError
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import ValidationError
import django.db
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from hotpocket_backend.apps.accounts.types import PAccount from hotpocket_backend.apps.accounts.types import PAccount
from hotpocket_soa.dto.saves import SaveIn from hotpocket_soa.dto.associations import AssociationOut
from hotpocket_soa.dto.celery import AsyncResultOut
from hotpocket_soa.dto.saves import SaveIn, SaveOut
from hotpocket_soa.services import SavesService
from .base import SaveWorkflow from .base import SaveWorkflow
class CreateSaveWorkflow(SaveWorkflow): class CreateSaveWorkflow(SaveWorkflow):
def create_associate_and_process(self,
account: PAccount,
url: str,
) -> tuple[SaveOut, AssociationOut, AsyncResultOut | None]:
with django.db.transaction.atomic():
save = self.create(
account.pk,
SaveIn(url=url),
)
association = self.associate(account.pk, save)
processing_result: AsyncResultOut | None = None
if save.last_processed_at is None:
processing_result = self.schedule_processing(save)
return (save, association, processing_result)
def run(self, def run(self,
*, *,
request: HttpRequest, request: HttpRequest,
@@ -21,16 +45,10 @@ class CreateSaveWorkflow(SaveWorkflow):
url: str, url: str,
force_post_save: bool = False, force_post_save: bool = False,
) -> HttpResponse: ) -> HttpResponse:
save = self.create( save, association, processing_result = self.create_associate_and_process(
account.pk, account, url,
SaveIn(url=url),
) )
association = self.associate(account.pk, save)
if save.last_processed_at is None:
processing_result = self.schedule_processing(save) # noqa: F841
response = redirect(reverse('ui.associations.browse')) response = redirect(reverse('ui.associations.browse'))
if force_post_save is True or save.is_netloc_banned is True: if force_post_save is True or save.is_netloc_banned is True:
response = redirect(reverse( response = redirect(reverse(
@@ -48,3 +66,21 @@ class CreateSaveWorkflow(SaveWorkflow):
response.headers['X-HotPocket-Testing-Association-PK'] = association.pk response.headers['X-HotPocket-Testing-Association-PK'] = association.pk
return response return response
def run_rpc(self,
*,
request: HttpRequest,
account: PAccount,
url: str,
) -> AssociationOut:
try:
save, association, processing_result = self.create_associate_and_process(
account, url,
)
return association
except SavesService.SavesServiceError as exception:
if isinstance(exception.__cause__, ValidationError) is True:
raise JSONRPCInternalError(data=exception.__cause__)
raise

View File

@@ -23,7 +23,6 @@ body, html {
} }
body > .ui-viewport { body > .ui-viewport {
padding-bottom: 3.5rem;
padding-top: 3.5rem; padding-top: 3.5rem;
} }
@@ -36,7 +35,7 @@ body:not(.ui-js-enabled) .ui-noscript-hide {
} }
#navbar .ui-navbar-brand > img { #navbar .ui-navbar-brand > img {
border-radius: 0.25rem; border-radius: 20%;
height: 1.5rem; height: 1.5rem;
vertical-align: top; vertical-align: top;
} }
@@ -45,11 +44,11 @@ body:not(.ui-js-enabled) .ui-noscript-hide {
height: 100%; height: 100%;
} }
.ui-save-card .card-footer .spinner-border { .spinner-border.ui-htmx-indicator {
display: none; display: none;
} }
.ui-save-card .card-footer .spinner-border.htmx-request { .spinner-border.ui-htmx-indicator.htmx-request {
display: block; display: block;
} }
@@ -103,3 +102,23 @@ body.ui-mode-standalone #offcanvas-controls .offcanvas-body {
#BrowseSavesView-Search .form-select { #BrowseSavesView-Search .form-select {
min-width: 10rem; min-width: 10rem;
} }
#navbar .ui-inline-create-save-form {
bottom: 0px;
right: 0px;
top: 0px;
width: 500px;
}
@media screen and (max-width: 767px) {
#navbar .ui-inline-create-save-form {
right: 0.5rem;
width: calc(100vw - 0.5rem - 0.5rem);
}
}
@media screen and (min-width: 768px) {
#InlineCreateSaveForm {
position: relative;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -0,0 +1,63 @@
/*!
* HotPocket by BTHLabs (https://hotpocket.app/)
* Copyright 2025-present BTHLabs <contact@bthlabs.pl> (https://bthlabs.pl/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
((HotPocket, htmx) => {
class BrowseAccountAppsView {
constructor (app) {
this.app = app;
}
onLoad = (event) => {
document.addEventListener(
'HotPocket:BrowseAccountAppsView:updateLoadMoreButton',
this.onUpdateLoadMoreButton,
);
document.addEventListener(
'HotPocket:BrowseAccountAppsView:delete',
this.onDelete,
);
};
onUpdateLoadMoreButton = (event) => {
const button = document.querySelector('#BrowseAccountAppsView .ui-load-more-button');
if (button) {
if (event.detail.next_url) {
button.setAttribute('hx-get', event.detail.next_url);
button.classList.remove('disable');
button.removeAttribute('disabled', true);
} else {
button.classList.add('disable');
button.setAttribute('disabled', true);
}
htmx.process('#BrowseAccountAppsView .ui-load-more-button');
}
};
onDelete = (event) => {
if (event.detail && event.detail.pk) {
const elementsToRemove = document.querySelectorAll(
`[data-access-token="${event.detail.pk}"]`,
);
for (let elementToRemove of elementsToRemove) {
elementToRemove.remove();
}
}
};
}
HotPocket.addPlugin('UI.BrowseAccountAppsView', (app) => {
return new BrowseAccountAppsView(app);
});
})(window.HotPocket, window.htmx);

View File

@@ -20,21 +20,25 @@
this.app = app; this.app = app;
} }
onLoad = (event) => { onLoad = (event) => {
document.addEventListener('HotPocket:BrowseSavesView:updateLoadMoreButton', (event) => { document.addEventListener(
const button = document.querySelector('#BrowseSavesView .ui-load-more-button'); 'HotPocket:BrowseSavesView:updateLoadMoreButton',
if (button) { this.onUpdateLoadMoreButton,
if (event.detail.next_url) { );
button.setAttribute('hx-get', event.detail.next_url); };
button.classList.remove('disable'); onUpdateLoadMoreButton = (event) => {
button.removeAttribute('disabled', true); const button = document.querySelector('#BrowseSavesView .ui-load-more-button');
} else { if (button) {
button.classList.add('disable'); if (event.detail.next_url) {
button.setAttribute('disabled', true); button.setAttribute('hx-get', event.detail.next_url);
} button.classList.remove('disable');
button.removeAttribute('disabled', true);
htmx.process('#BrowseSavesView .ui-load-more-button'); } else {
button.classList.add('disable');
button.setAttribute('disabled', true);
} }
});
htmx.process('#BrowseSavesView .ui-load-more-button');
}
}; };
} }

Some files were not shown because too many files have changed in this diff Show More