Compare commits

...
Sign in to create a new pull request.

42 commits

Author SHA1 Message Date
5233b4518f add request size limit notifications 2026-07-18 15:06:20 +02:00
9c99b112d5 forgot to add 2026-07-17 11:40:21 +02:00
50ebf57b4c fix icons size and message update formatting & add missing blahs 2026-07-17 11:40:04 +02:00
0a20562d9f dodałem jeżyk polski 2026-07-16 20:12:17 +02:00
c1ac9ea142 dodałem polski do indeksu 2026-07-16 17:24:38 +02:00
3a9ff75b8f move icons to separate file 2026-07-16 17:22:29 +02:00
1800b89a6b add attachments 2026-07-14 19:26:26 +02:00
0bdfb7762f fix opening dms from profile and profile scroll saving 2026-07-14 17:23:10 +02:00
fb6c8b6ee9 update electron to v43 2026-07-09 22:38:33 +02:00
68ac0beedf add message read/unread render & fix profile display 2026-07-04 17:24:46 +02:00
271c573fb4 add better loading for profile settings and user profiles & add details bar 2026-07-04 13:55:23 +02:00
95aaaa69ea big updat:
- update dependencies
- add webp support and webp conversion for profile images
2026-07-02 22:40:46 +02:00
19c3dbb42d fix aspect ratio and cropping on small screens 2026-06-24 00:11:37 +02:00
cc41f25277 Add settings and profile editor 2026-06-23 23:41:27 +02:00
80f11f782e Jebane kurwa gesty blokuja mi przycisk 2026-06-23 00:46:14 +02:00
3f99389413 better context menu reposition, add more profile actions 2026-06-19 22:17:33 +02:00
516bdf5186 Add profile context menu & test profile popup 2026-06-19 20:04:23 +02:00
d587aed92c fix emoji picker styling & update packages 2026-06-18 08:40:01 +02:00
ecf7e3b4ce add better reaction popup 2026-06-18 08:11:38 +02:00
ae33a8a61b Fix "messages.loading.older" message & fix app crash on unsupported blahs 2026-06-18 07:28:27 +02:00
3f0f628569 Fixed context menu flicker, resize scrolling and race conditions 2026-06-13 07:19:12 +02:00
4016975900 Changes:
- add replies
- add reactions
- add message redacting
- fix chat height when opening mobile keyboard
2026-06-12 16:51:32 +02:00
75cd78ed5a update packages and android build script 2026-06-12 12:55:11 +02:00
802333a7aa Add fetching older messages 2026-06-03 12:46:36 +02:00
2e2c080df4 Forgot to fix keyboard 2026-06-03 01:03:02 +02:00
005d2e2820 Fix ui bugs:
- keyboard screen glitch
- multi-line messages
- missing styling
2026-06-03 00:56:29 +02:00
92edb123f3 working messages!!@!@ 2026-06-02 12:53:41 +02:00
b24b36adec Fix race conditions 2026-06-02 11:58:18 +02:00
aa0351fb2d Add key mismatch error handling 2026-05-30 11:05:10 +02:00
bba80a19df Change send message icon & slightly tweak border radius 2026-05-30 10:31:17 +02:00
c5004dd52b test installer (fix taksbar icon) 2026-05-29 22:38:05 +02:00
5c91c341a0 improved ui and added missing blahs 2026-05-29 10:13:58 +02:00
f957a081f3 add message reading 2026-05-29 09:32:24 +02:00
be26703908 add DM screen 2026-05-29 08:54:18 +02:00
a666cb9915 blahs update 2026-05-28 13:19:39 +02:00
8f28879f18 DM keys!!! 2026-05-27 23:45:38 +02:00
a660ba32bd Switch to X25519 + ML-KEM-768 encryption 2026-05-27 20:40:24 +02:00
9e6d128839 Make most requests use id instead of username 2026-05-27 19:41:30 +02:00
2979881908 Add test dm accepting, revoking and declining 2026-05-27 08:53:24 +02:00
81e222250d Add fetching invites 2026-05-21 11:02:02 +02:00
81145968a1 Add blahs in actions 2026-05-21 08:28:06 +02:00
5a9875ea01 Merge pull request 'blahy (teraz mam wiecej blahow niz mumin :3)' (#1) from sugary/LarpixClient:main into main
Reviewed-on: olcxjas-softworks/LarpixClient#1
2026-05-20 13:16:39 +02:00
7209 changed files with 675460 additions and 125127 deletions

View file

@ -33,7 +33,7 @@ jobs:
run: npx cap copy electron
- name: Build
run: mkdir -p build && cd build && npx electron-packager ../electron miarven --platform=linux --arch=x64 --icon=../icons/icon.png --overwrite --asar && cp ../icons/icon.png ./miarven-linux-x64/icon.png && cp ../assets/Miarven.desktop ./miarven-linux-x64/Miarven.desktop
run: mkdir -p build && cd build && npx electron-packager ../electron miarven --platform=linux --arch=x64 --icon=../icons/icon.png --overwrite --asar && cp ../icons/icon.png ./miarven-linux-x64/icon.png && cp ../assets/olcxja.miarven.desktop ./miarven-linux-x64/olcxja.miarven.desktop && cp ../assets/install.sh ./miarven-linux-x64/install.sh && chmod +x ./miarven-linux-x64/install.sh
- name: Upload Artifact
uses: actions/upload-artifact@v3

2
.npmrc Normal file
View file

@ -0,0 +1,2 @@
min-release-age=21
ignore-scripts=true

View file

@ -0,0 +1,198 @@
{
"user.not.found": "meowser not found :c",
"dm.already.exists": "direct meowchat already exists :3",
"cant.invite.urself": "you can't invite yourself, silly :3",
"user.already.invited": "you have already invited this user meow",
"user.invited": "user invited successfully :3",
"cant.create.dm.without.invitation": "you can't create a dm without invitation ><",
"dm.begin.notice": "at the beginning of a meowchat, you should always verify that the person you're talking to is the intended recipient",
"failed.accept.dm": "failed to accept direct meowchat",
"dm.accepted": "direct meowchat accepted",
"no.invite.found": "you can't create a dm without invitation *hiss*",
"unknown.error": "unknown error :o",
"forbidden": "you do not have pawmission to perform this action *hiss*",
"dm.key.too.large": "meowchat key too large :o",
"dm.key.not.found": "meowchat key not found :c",
"dm.key.updated": "meowchat key updated successfully :3",
"body.too.large": "Meow is too big! Max meow is {0} MB.",
"message.too.long": "Meowssage exceeds maximum allowed size ({0}).",
"reaction.too.long": "Re-meow-ction exceeds maximum allowed size ({0}).",
"invite.revoked": "invite revoked successfully",
"invite.declined": "invite declined successfully",
"account.creation.request.expired": "cat creation request expired. try again",
"invalid.username": "invalid cat name: {0}",
"invalid.password": "invalid meow word: {0}",
"incorrect.captcha": "incorrect captcha. try meowgain",
"username.taken": "this cat name is already taken",
"accounts.slots.full": "you can't create a new cat because all collars are used. try again later",
"registration.disabled": "registration disabled",
"account.created": "cat created successfully",
"password.changed": "meow word changed successfully",
"keys.updated": "nametag updated successfully",
"unknown.request": "unknown request: {0}",
"login.successful": "pawgin successful :3",
"username.length": "cat name must be {0} characters long",
"username.conditions.allowed": "cat name can only include {all}",
"password.not.hashed.properly": "meow word is not hashed properly",
"invalid.username.or.password": "invalid cat name or meow word",
"error.storage.limit.exceeded": "Storage Limit Exceeded, purr",
"error.network.error": "Network error, check connection or file size, meow.",
"account.not.exist": "cat with this name doesn't exist",
"invalid.nonce": "invalid nonce. try again",
"username.changed": "cat name changed successfully",
"auth.failed.redirect.to.login": "failed to find owner. redirecting to pawgin...",
"dm.refresh.failed": "failed to refresh direct meowchats",
"chat.add.failed": "failed to add meowchat",
"something.wrong.mayb.pass": "something went wrong... :c (probably wrong meow word)",
"something.wrong": "something went wrong... :c",
"passwords.not.match": "meow word do not match",
"password.cant.empty": "meow word cannot be empty",
"username.cant.empty": "cat name cannot be empty",
"bad.request": "bad request!!!",
"letters": "letters",
"numbers": "numbers",
"underscores": "underscores",
"loading.connecting": "connecting...",
"loading.loading": "loading...",
"loading.done": "ready! :3",
"title.home": "bed",
"title.dms": "direct meowchats",
"title.groups": "clowder",
"title.welcome.splash": "welpaw to Meowven",
"desc.welcome.splash": "first Larpix client",
"title.splash": "splash",
"title.create.dm": "invite to direct meowchat",
"title.create.space": "create house",
"title.create.group": "create clowder",
"title.add.chat": "add meowchat",
"desc.add.chat": "add a private, encrypted meowchat by entering a cat name",
"desc.create.group": "create a private, encrypted clowder",
"placeholder.create.group.input": "my clowder",
"placeholder.create.space.input": "my house",
"title.name": "name",
"desc.create.space": "create a house for your community",
"desc.join.space": "join a community that fits you",
"title.join.space": "join house",
"title.space.id": "house id",
"title.inbox": "inbox",
"title.invites": "invites",
"title.notifications": "notifications",
"title.received": "received",
"title.sent": "sent",
"title.all": "all",
"title.unread": "unread",
"desc.no.invites": "no invites found :c",
"desc.no.notifications": "no notifications found :c",
"desc.fetching.invites": "fetching invites...",
"desc.fetching.notifications": "fetching notifications...",
"desc.invite.dm.received": "{0} ({1}) invited you to meowchat",
"desc.invite.dm.sent": "you invited {0} ({1}) to meowchat",
"desc.invite.group.received": "{0} ({1}) invited you to a clowder",
"desc.invite.group.sent": "you invited {0} ({1}) to a clowder",
"action.invite.sending": "Sending mewvite...",
"action.processing": "Purrcessing...",
"action.fetching.invites.sent": "Fetching sent mewvites...",
"action.fetching.invites.recv": "fetching received invites...",
"action.dm.fetch": "fetching direct meowchats...",
"action.auth": "authenticating...",
"action.dm.adding": "adding...",
"action.invite.revoking": "revoking invite...",
"action.invite.accepting": "accepting invite...",
"action.invite.declining": "declining invite...",
"title.sign.up": "sign up",
"title.sign.in": "sign in",
"title.sign.in.to": "sign in to your larpix instance",
"title.server.host": "server host",
"title.username": "meow name",
"title.password": "meow word",
"title.new.here.?": "new here?",
"title.create.an.account": "create a cat",
"join.larpix": "join Larpix",
"title.create.new.account": "create new larpix cat account",
"notice.use.strong.pass": "remember to use a strong meow word! tt will be used to lock your collar",
"title.confirm.password": "confirm meow word",
"title.already.registered.?": "already a kitty?",
"title.back.to.login": "back to pawgin",
"title.verify": "verify",
"title.captcha.desc": "prove you are a kitty",
"title.captcha.code": "captcha code",
"title.invitation.code": "invitation code",
"title.back.to.register": "back to registration",
"title.details": "details",
"title.members": "cats",
"placeholder.username": "cat name",
"placeholder.captcha.code": "captcha code",
"desc.messages.loading": "loading meows...",
"desc.no.dms": "no direct meowchats :c",
"action.dm.opening": "opening meowchat...",
"dm.open.failed": "failed to open meowchat :c",
"keys.local.server.mismatch": "this device has different nametags than server :c use the device u first logged in on",
"keys.server.decrypt.failed": "wrong password for ur secret nametag bundle :c",
"dm.messages.fetch.failed": "failed to fetch meows :c",
"messages.decrypt.failed": "failed to decrypt meow :c",
"title.reply.message": "meow back",
"title.react.message": "purr",
"title.text.react": "text purr",
"title.delete.message": "hiss away",
"title.replied.to": "meowed back to",
"title.replying.to": "meowing back to",
"action.message.deleting": "hissing message away...",
"action.message.sending": "sending meow...",
"action.message.reacting": "adding purr...",
"error:message.delete.failed": "failed to hiss away meow",
"error:message.send.failed": "failed to send meow",
"error:message.react.failed": "failed to add purr",
"info.sending.message": "sending...",
"placeholder.message.input": "meow...",
"larp.redacted": "Meow meow was meowed.",
"message.already.deleted": "This meow has already been meowed.",
"rate.limit": "You are meowing too quickly. Please meow again later.",
"placeholder.invitation.code": "meow meow meow",
"info.messages.loading.older": "Loading older meowsages...",
"error:message.not.found": "Meowsage not found",
"title.profile": "cat profile",
"title.settings": "cat settings",
"title.logout": "log out",
"title.loading": "sniffing...",
"action.edit.profile": "edit cat profile",
"action.open.dm": "open direct meowchat",
"action.invite.accept": "accept meow invite",
"action.invite.cancel": "hiss away invite",
"action.invite.send": "invite to meowchat",
"action.profile.loading": "sniffing profile...",
"error.profile.load.failed": "failed to sniff profile.",
"unknown.author": "somecat",
"title.account": "Cat",
"title.delete.account": "Delete cat",
"desc.delete.account": "Once you delete your cat, it will go to cat heaven. Please be certain :c",
"title.avatar": "Cat picture",
"title.banner": "Cat banner",
"title.bio": "Cat bio",
"title.appearance": "Appearance",
"title.language": "Meownguage",
"action.save.profile": "Save cat profile",
"action.save.bio": "Save meow-io",
"action.uploading": "Uploading...",
"action.bio.saving": "Saving meow-io...",
"action.account.saving": "Saving cat...",
"action.account.deleting": "Deleting cat...",
"success:profile.updated": "Cat profile updated successfully :3",
"success:avatar.updated": "Cat picture updated successfully :3",
"success:banner.updated": "Cat banner updated successfully :3",
"action.save": "Save :3",
"action.delete.account": "Delete cat",
"account.deleted.scheduled": "Cat scheduled for deletion. It will go to cat heaven after a month of inactivity. Pawing in again will cancel the deletion process :3",
"action.attachment.take.photo": "Meow photo",
"action.attachment.media": "Meowdia",
"action.attachment.file": "Meowle",
"action.attachment.poll": "Meowll",
"action.attachment.uploading": "Uploading meowttachments...",
"attachment.legacy": "Legacy meowttachment",
"warning.unencrypted": "This meowsage is not encrypted",
"action.change.avatar": "Change Cat Picture",
"action.change.banner": "Change Cat Banner",
"placeholder.type.text.emoji": "Type purr / meow...",
"action.cancel": "Cancel",
"title.crop.image": "Crop Image",
"title.zoom": "Zoom"
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,5 @@
{
"en-us": { "english": "English (US)", "native": "English (US)" },
"en-cat": { "english": "English (Cat)", "native": "Meowlish" },
"pl-pl": { "english": "Polish", "native": "Polski" }
}

View file

@ -0,0 +1,198 @@
{
"user.not.found": "Nie znaleziono użytkownika",
"dm.already.exists": "Wiadomość prywatna już istnieje",
"cant.invite.urself": "Nie możesz zaprosić samego siebie",
"user.already.invited": "Ten użytkownik został już przez Ciebie zaproszony",
"user.invited": "Użytkownik został pomyślnie zaproszony",
"cant.create.dm.without.invitation": "Nie możesz utworzyć wiadomości prywatnej bez zaproszenia",
"dm.begin.notice": "Na początku każdej rozmowy prywatnej zawsze upewnij się, że rozmawiasz z właściwą osobą",
"failed.accept.dm": "Nie udało się zaakceptować rozmowy prywatnej",
"dm.accepted": "Rozmowa prywatna została zaakceptowana",
"no.invite.found": "Nie możesz utworzyć wiadomości prywatnej bez zaproszenia",
"unknown.error": "Nieznany błąd",
"forbidden": "Nie masz uprawnień do wykonania tej akcji",
"dm.key.too.large": "Klucz wiadomości prywatnej jest zbyt duży",
"dm.key.not.found": "Nie znaleziono klucza wiadomości prywatnej",
"dm.key.updated": "Klucz wiadomości prywatnej został pomyślnie zaktualizowany",
"body.too.large": "Treść żądania jest zbyt duża. Maksymalny limit to {0} MB",
"message.too.long": "Wiadomość przekracza dozwolony rozmiar ({0}).",
"reaction.too.long": "Reakcja przekracza dozwolony rozmiar ({0}).",
"invite.revoked": "Zaproszenie zostało cofnięte",
"invite.declined": "Zaproszenie zostało odrzucone",
"account.creation.request.expired": "Żądanie utworzenia konta wygasło. Spróbuj ponownie",
"invalid.username": "Nieprawidłowa nazwa użytkownika: {0}",
"invalid.password": "Nieprawidłowe hasło: {0}",
"incorrect.captcha": "Nieprawidłowa captcha. Spróbuj ponownie",
"username.taken": "Ta nazwa użytkownika jest już zajęta",
"accounts.slots.full": "Nie można utworzyć nowego konta, ponieważ wszystkie identyfikatory są zajęte. Spróbuj ponownie później",
"registration.disabled": "Rejestracja jest wyłączona",
"account.created": "Konto zostało pomyślnie utworzone",
"password.changed": "Hasło zostało pomyślnie zmienione",
"keys.updated": "Klucze zostały pomyślnie zaktualizowane",
"unknown.request": "Nieznane żądanie: {0}",
"login.successful": "Logowanie zakończone pomyślnie",
"username.length": "Nazwa użytkownika musi mieć {0} znaków",
"username.conditions.allowed": "Nazwa użytkownika może zawierać tylko: {all}",
"password.not.hashed.properly": "Hasło nie zostało poprawnie zahaszowane",
"invalid.username.or.password": "Nieprawidłowa nazwa użytkownika lub hasło",
"error.storage.limit.exceeded": "Przekroczono limit pamięci",
"error.network.error": "Błąd sieci. Sprawdź połączenie lub rozmiar pliku.",
"account.not.exist": "Konto o tej nazwie nie istnieje",
"invalid.nonce": "Nieprawidłowy nonce. Spróbuj ponownie",
"username.changed": "Nazwa użytkownika została pomyślnie zmieniona",
"auth.failed.redirect.to.login": "Uwierzytelnianie nie powiodło się. Przekierowywanie do logowania...",
"dm.refresh.failed": "Nie udało się odświeżyć rozmów prywatnych",
"chat.add.failed": "Nie udało się dodać czatu",
"something.wrong.mayb.pass": "Coś poszło nie tak... (prawdopodobnie błędne hasło)",
"something.wrong": "Coś poszło nie tak...",
"passwords.not.match": "Hasła nie są zgodne",
"password.cant.empty": "Hasło nie może być puste",
"username.cant.empty": "Nazwa użytkownika nie może być pusta",
"bad.request": "Nieprawidłowe żądanie",
"letters": "litery",
"numbers": "cyfry",
"underscores": "podkreślenia",
"loading.connecting": "Łączenie...",
"loading.loading": "Ładowanie...",
"loading.done": "Gotowe!",
"title.home": "Strona główna",
"title.dms": "Wiadomości prywatne",
"title.groups": "Grupy",
"title.welcome.splash": "Witamy w Miarven",
"desc.welcome.splash": "Pierwszy klient Larpix",
"title.splash": "Ekran powitalny",
"title.create.dm": "Zaproś do rozmowy prywatnej",
"title.create.space": "Utwórz społeczność",
"title.create.group": "Utwórz grupę",
"title.add.chat": "Dodaj czat",
"desc.add.chat": "Dodaj prywatny, szyfrowany czat, wpisując nazwę użytkownika",
"desc.create.group": "Utwórz prywatną, szyfrowaną grupę",
"placeholder.create.group.input": "Moja grupa",
"placeholder.create.space.input": "Moja społeczność",
"title.name": "Nazwa",
"desc.create.space": "Utwórz społeczność dla swojej grupy",
"desc.join.space": "Dołącz do społeczności, która Ci odpowiada",
"title.join.space": "Dołącz do społeczności",
"title.space.id": "Identyfikator społeczności",
"title.inbox": "Skrzynka odbiorcza",
"title.invites": "Zaproszenia",
"title.notifications": "Powiadomienia",
"title.received": "Odebrane",
"title.sent": "Wysłane",
"title.all": "Wszystkie",
"title.unread": "Nieprzeczytane",
"desc.no.invites": "Brak zaproszeń",
"desc.no.notifications": "Brak powiadomień",
"desc.fetching.invites": "Pobieranie zaproszeń...",
"desc.fetching.notifications": "Pobieranie powiadomień...",
"desc.invite.dm.received": "{0} ({1}) zaprosił(a) Cię do rozmowy",
"desc.invite.dm.sent": "Zaprosiłeś(-aś) {0} ({1}) do rozmowy",
"desc.invite.group.received": "{0} ({1}) zaprosił(a) Cię do grupy",
"desc.invite.group.sent": "Zaprosiłeś(-aś) {0} ({1}) do grupy",
"action.invite.sending": "Wysyłanie zaproszenia...",
"action.processing": "Przetwarzanie...",
"action.fetching.invites.sent": "Pobieranie wysłanych zaproszeń...",
"action.fetching.invites.recv": "Pobieranie otrzymanych zaproszeń...",
"action.dm.fetch": "Pobieranie rozmów prywatnych...",
"action.auth": "Uwierzytelnianie...",
"action.dm.adding": "Dodawanie...",
"action.invite.revoking": "Cofanie zaproszenia...",
"action.invite.accepting": "Akceptowanie zaproszenia...",
"action.invite.declining": "Odrzucanie zaproszenia...",
"title.sign.up": "Zarejestruj się",
"title.sign.in": "Zaloguj się",
"title.sign.in.to": "Zaloguj się do swojej instancji Larpix",
"title.server.host": "Adres serwera",
"title.username": "Nazwa użytkownika",
"title.password": "Hasło",
"title.new.here.?": "Nowy tutaj?",
"title.create.an.account": "Utwórz konto",
"join.larpix": "Dołącz do Larpix",
"title.create.new.account": "Utwórz nowe konto Larpix",
"notice.use.strong.pass": "Pamiętaj o użyciu silnego hasła! Posłuży ono do szyfrowania Twoich kluczy.",
"title.confirm.password": "Potwierdź hasło",
"title.already.registered.?": "Masz już konto?",
"title.back.to.login": "Powrót do logowania",
"title.verify": "Weryfikacja",
"title.captcha.desc": "Udowodnij, że jesteś człowiekiem",
"title.captcha.code": "Kod CAPTCHA",
"title.invitation.code": "Kod zaproszenia",
"title.back.to.register": "Powrót do rejestracji",
"placeholder.username": "nazwa użytkownika",
"placeholder.captcha.code": "kod captcha",
"placeholder.message.input": "Wiadomość...",
"desc.messages.loading": "Pobieranie wiadomości...",
"desc.no.dms": "Brak wiadomości prywatnych",
"action.dm.opening": "Otwieranie rozmowy...",
"dm.open.failed": "Nie udało się otworzyć rozmowy",
"keys.local.server.mismatch": "To urządzenie ma inne klucze szyfrowania niż serwer. Użyj urządzenia, na którym zalogowano się po raz pierwszy, lub przywróć klucze z kopii zapasowej.",
"keys.server.decrypt.failed": "Nie udało się odszyfrować kluczy konta przy użyciu tego hasła.",
"dm.messages.fetch.failed": "Nie udało się pobrać wiadomości",
"messages.decrypt.failed": "Nie udało się odszyfrować wiadomości",
"title.reply.message": "Odpowiedz",
"title.react.message": "Zareaguj",
"title.text.react": "Reakcja tekstowa",
"title.delete.message": "Usuń",
"title.replied.to": "W odpowiedzi do",
"title.replying.to": "Odpowiadasz na",
"action.message.deleting": "Usuwanie wiadomości...",
"action.message.sending": "Wysyłanie wiadomości...",
"action.message.reacting": "Dodawanie reakcji...",
"error:message.delete.failed": "Nie udało się usunąć wiadomości",
"error:message.send.failed": "Nie udało się wysłać wiadomości",
"error:message.react.failed": "Nie udało się dodać reakcji",
"info.sending.message": "Wysyłanie...",
"larp.redacted": "Ta wiadomość została usunięta.",
"message.already.deleted": "Ta wiadomość została już usunięta.",
"rate.limit": "Wysyłasz żądania zbyt szybko. Spróbuj ponownie później.",
"placeholder.invitation.code": "kod zaproszenia",
"info.messages.loading.older": "Ładowanie starszych wiadomości...",
"error:message.not.found": "Nie znaleziono wiadomości",
"title.profile": "Profil",
"title.settings": "Ustawienia",
"title.logout": "Wyloguj",
"title.loading": "Ładowanie...",
"title.details": "Szczegóły",
"title.members": "Członkowie",
"action.edit.profile": "Edytuj profil",
"action.open.dm": "Otwórz rozmowę",
"action.invite.accept": "Akceptuj zaproszenie",
"action.invite.cancel": "Anuluj zaproszenie",
"action.invite.send": "Zaproś do rozmowy",
"action.profile.loading": "Ładowanie profilu...",
"error.profile.load.failed": "Nie udało się załadować profilu.",
"unknown.author": "Ktoś",
"title.account": "Konto",
"title.delete.account": "Usuń konto",
"desc.delete.account": "Po usunięciu konta nie będzie możliwości jego odzyskania. Upewnij się, że na pewno chcesz to zrobić.",
"title.avatar": "Awatar",
"title.banner": "Baner",
"title.bio": "Opis",
"title.appearance": "Wygląd",
"title.language": "Język",
"action.save.profile": "Zapisz profil",
"action.save.bio": "Zapisz opis",
"action.uploading": "Przesyłanie...",
"action.bio.saving": "Zapisywanie opisu...",
"action.account.saving": "Zapisywanie konta...",
"action.account.deleting": "Usuwanie konta...",
"profile.updated": "Profil został pomyślnie zaktualizowany",
"avatar.updated": "Awatar został pomyślnie zaktualizowany",
"banner.updated": "Baner został pomyślnie zaktualizowany",
"action.save": "Zapisz",
"action.delete.account": "Usuń konto",
"account.deleted.scheduled": "Konto zostało oznaczone do usunięcia. Zostanie trwale usunięte po miesiącu braku aktywności. Zalogowanie się w tym czasie anuluje proces usuwania.",
"action.attachment.take.photo": "Zrób zdjęcie",
"action.attachment.media": "Multimedia",
"action.attachment.file": "Plik",
"action.attachment.poll": "Ankieta",
"action.attachment.uploading": "Przesyłanie załączników...",
"attachment.legacy": "Starszy załącznik",
"warning.unencrypted": "Ta wiadomość nie jest szyfrowana",
"action.change.avatar": "Zmień awatar",
"action.change.banner": "Zmień baner",
"placeholder.type.text.emoji": "Wpisz tekst / emoji...",
"action.cancel": "Anuluj",
"title.crop.image": "Przytnij obraz",
"title.zoom": "Przybliżenie"
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
Couldn't find the requested file /dist/gifuct-js.min.js in gifuct-js.

View file

@ -0,0 +1,578 @@
"use strict";
var gifuct = (() => {
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
try {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
} catch (e) {
throw mod = 0, e;
}
};
// ../node_modules/js-binary-schema-parser/lib/index.js
var require_lib = __commonJS({
"../node_modules/js-binary-schema-parser/lib/index.js"(exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.loop = exports.conditional = exports.parse = void 0;
var parse = function parse2(stream, schema) {
var result = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
var parent = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : result;
if (Array.isArray(schema)) {
schema.forEach(function(partSchema) {
return parse2(stream, partSchema, result, parent);
});
} else if (typeof schema === "function") {
schema(stream, result, parent, parse2);
} else {
var key = Object.keys(schema)[0];
if (Array.isArray(schema[key])) {
parent[key] = {};
parse2(stream, schema[key], result, parent[key]);
} else {
parent[key] = schema[key](stream, result, parent, parse2);
}
}
return result;
};
exports.parse = parse;
var conditional = function conditional2(schema, conditionFunc) {
return function(stream, result, parent, parse2) {
if (conditionFunc(stream, result, parent)) {
parse2(stream, schema, result, parent);
}
};
};
exports.conditional = conditional;
var loop = function loop2(schema, continueFunc) {
return function(stream, result, parent, parse2) {
var arr = [];
var lastStreamPos = stream.pos;
while (continueFunc(stream, result, parent)) {
var newParent = {};
parse2(stream, schema, result, newParent);
if (stream.pos === lastStreamPos) {
break;
}
lastStreamPos = stream.pos;
arr.push(newParent);
}
return arr;
};
};
exports.loop = loop;
}
});
// ../node_modules/js-binary-schema-parser/lib/parsers/uint8.js
var require_uint8 = __commonJS({
"../node_modules/js-binary-schema-parser/lib/parsers/uint8.js"(exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.readBits = exports.readArray = exports.readUnsigned = exports.readString = exports.peekBytes = exports.readBytes = exports.peekByte = exports.readByte = exports.buildStream = void 0;
var buildStream = function buildStream2(uint8Data) {
return {
data: uint8Data,
pos: 0
};
};
exports.buildStream = buildStream;
var readByte = function readByte2() {
return function(stream) {
return stream.data[stream.pos++];
};
};
exports.readByte = readByte;
var peekByte = function peekByte2() {
var offset = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0;
return function(stream) {
return stream.data[stream.pos + offset];
};
};
exports.peekByte = peekByte;
var readBytes = function readBytes2(length) {
return function(stream) {
return stream.data.subarray(stream.pos, stream.pos += length);
};
};
exports.readBytes = readBytes;
var peekBytes = function peekBytes2(length) {
return function(stream) {
return stream.data.subarray(stream.pos, stream.pos + length);
};
};
exports.peekBytes = peekBytes;
var readString = function readString2(length) {
return function(stream) {
return Array.from(readBytes(length)(stream)).map(function(value) {
return String.fromCharCode(value);
}).join("");
};
};
exports.readString = readString;
var readUnsigned = function readUnsigned2(littleEndian) {
return function(stream) {
var bytes = readBytes(2)(stream);
return littleEndian ? (bytes[1] << 8) + bytes[0] : (bytes[0] << 8) + bytes[1];
};
};
exports.readUnsigned = readUnsigned;
var readArray = function readArray2(byteSize, totalOrFunc) {
return function(stream, result, parent) {
var total = typeof totalOrFunc === "function" ? totalOrFunc(stream, result, parent) : totalOrFunc;
var parser = readBytes(byteSize);
var arr = new Array(total);
for (var i = 0; i < total; i++) {
arr[i] = parser(stream);
}
return arr;
};
};
exports.readArray = readArray;
var subBitsTotal = function subBitsTotal2(bits, startIndex, length) {
var result = 0;
for (var i = 0; i < length; i++) {
result += bits[startIndex + i] && Math.pow(2, length - i - 1);
}
return result;
};
var readBits = function readBits2(schema) {
return function(stream) {
var _byte = readByte()(stream);
var bits = new Array(8);
for (var i = 0; i < 8; i++) {
bits[7 - i] = !!(_byte & 1 << i);
}
return Object.keys(schema).reduce(function(res, key) {
var def = schema[key];
if (def.length) {
res[key] = subBitsTotal(bits, def.index, def.length);
} else {
res[key] = bits[def.index];
}
return res;
}, {});
};
};
exports.readBits = readBits;
}
});
// ../node_modules/js-binary-schema-parser/lib/schemas/gif.js
var require_gif = __commonJS({
"../node_modules/js-binary-schema-parser/lib/schemas/gif.js"(exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _ = require_lib();
var _uint = require_uint8();
var subBlocksSchema = {
blocks: function blocks(stream) {
var terminator = 0;
var chunks = [];
var streamSize = stream.data.length;
var total = 0;
for (var size = (0, _uint.readByte)()(stream); size !== terminator; size = (0, _uint.readByte)()(stream)) {
if (!size) break;
if (stream.pos + size >= streamSize) {
var availableSize = streamSize - stream.pos;
chunks.push((0, _uint.readBytes)(availableSize)(stream));
total += availableSize;
break;
}
chunks.push((0, _uint.readBytes)(size)(stream));
total += size;
}
var result = new Uint8Array(total);
var offset = 0;
for (var i = 0; i < chunks.length; i++) {
result.set(chunks[i], offset);
offset += chunks[i].length;
}
return result;
}
};
var gceSchema = (0, _.conditional)({
gce: [{
codes: (0, _uint.readBytes)(2)
}, {
byteSize: (0, _uint.readByte)()
}, {
extras: (0, _uint.readBits)({
future: {
index: 0,
length: 3
},
disposal: {
index: 3,
length: 3
},
userInput: {
index: 6
},
transparentColorGiven: {
index: 7
}
})
}, {
delay: (0, _uint.readUnsigned)(true)
}, {
transparentColorIndex: (0, _uint.readByte)()
}, {
terminator: (0, _uint.readByte)()
}]
}, function(stream) {
var codes = (0, _uint.peekBytes)(2)(stream);
return codes[0] === 33 && codes[1] === 249;
});
var imageSchema = (0, _.conditional)({
image: [{
code: (0, _uint.readByte)()
}, {
descriptor: [{
left: (0, _uint.readUnsigned)(true)
}, {
top: (0, _uint.readUnsigned)(true)
}, {
width: (0, _uint.readUnsigned)(true)
}, {
height: (0, _uint.readUnsigned)(true)
}, {
lct: (0, _uint.readBits)({
exists: {
index: 0
},
interlaced: {
index: 1
},
sort: {
index: 2
},
future: {
index: 3,
length: 2
},
size: {
index: 5,
length: 3
}
})
}]
}, (0, _.conditional)({
lct: (0, _uint.readArray)(3, function(stream, result, parent) {
return Math.pow(2, parent.descriptor.lct.size + 1);
})
}, function(stream, result, parent) {
return parent.descriptor.lct.exists;
}), {
data: [{
minCodeSize: (0, _uint.readByte)()
}, subBlocksSchema]
}]
}, function(stream) {
return (0, _uint.peekByte)()(stream) === 44;
});
var textSchema = (0, _.conditional)({
text: [{
codes: (0, _uint.readBytes)(2)
}, {
blockSize: (0, _uint.readByte)()
}, {
preData: function preData(stream, result, parent) {
return (0, _uint.readBytes)(parent.text.blockSize)(stream);
}
}, subBlocksSchema]
}, function(stream) {
var codes = (0, _uint.peekBytes)(2)(stream);
return codes[0] === 33 && codes[1] === 1;
});
var applicationSchema = (0, _.conditional)({
application: [{
codes: (0, _uint.readBytes)(2)
}, {
blockSize: (0, _uint.readByte)()
}, {
id: function id(stream, result, parent) {
return (0, _uint.readString)(parent.blockSize)(stream);
}
}, subBlocksSchema]
}, function(stream) {
var codes = (0, _uint.peekBytes)(2)(stream);
return codes[0] === 33 && codes[1] === 255;
});
var commentSchema = (0, _.conditional)({
comment: [{
codes: (0, _uint.readBytes)(2)
}, subBlocksSchema]
}, function(stream) {
var codes = (0, _uint.peekBytes)(2)(stream);
return codes[0] === 33 && codes[1] === 254;
});
var schema = [
{
header: [{
signature: (0, _uint.readString)(3)
}, {
version: (0, _uint.readString)(3)
}]
},
{
lsd: [{
width: (0, _uint.readUnsigned)(true)
}, {
height: (0, _uint.readUnsigned)(true)
}, {
gct: (0, _uint.readBits)({
exists: {
index: 0
},
resolution: {
index: 1,
length: 3
},
sort: {
index: 4
},
size: {
index: 5,
length: 3
}
})
}, {
backgroundColorIndex: (0, _uint.readByte)()
}, {
pixelAspectRatio: (0, _uint.readByte)()
}]
},
(0, _.conditional)({
gct: (0, _uint.readArray)(3, function(stream, result) {
return Math.pow(2, result.lsd.gct.size + 1);
})
}, function(stream, result) {
return result.lsd.gct.exists;
}),
// content frames
{
frames: (0, _.loop)([gceSchema, applicationSchema, commentSchema, imageSchema, textSchema], function(stream) {
var nextCode = (0, _uint.peekByte)()(stream);
return nextCode === 33 || nextCode === 44;
})
}
];
var _default = schema;
exports["default"] = _default;
}
});
// ../node_modules/gifuct-js/lib/deinterlace.js
var require_deinterlace = __commonJS({
"../node_modules/gifuct-js/lib/deinterlace.js"(exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.deinterlace = void 0;
var deinterlace = function deinterlace2(pixels, width) {
var newPixels = new Array(pixels.length);
var rows = pixels.length / width;
var cpRow = function cpRow2(toRow2, fromRow2) {
var fromPixels = pixels.slice(fromRow2 * width, (fromRow2 + 1) * width);
newPixels.splice.apply(newPixels, [toRow2 * width, width].concat(fromPixels));
};
var offsets = [0, 4, 2, 1];
var steps = [8, 8, 4, 2];
var fromRow = 0;
for (var pass = 0; pass < 4; pass++) {
for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) {
cpRow(toRow, fromRow);
fromRow++;
}
}
return newPixels;
};
exports.deinterlace = deinterlace;
}
});
// ../node_modules/gifuct-js/lib/lzw.js
var require_lzw = __commonJS({
"../node_modules/gifuct-js/lib/lzw.js"(exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.lzw = void 0;
var lzw = function lzw2(minCodeSize, data, pixelCount) {
var MAX_STACK_SIZE = 4096;
var nullCode = -1;
var npix = pixelCount;
var available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, i, datum, data_size, first, top, bi, pi;
var dstPixels = new Array(pixelCount);
var prefix = new Array(MAX_STACK_SIZE);
var suffix = new Array(MAX_STACK_SIZE);
var pixelStack = new Array(MAX_STACK_SIZE + 1);
data_size = minCodeSize;
clear = 1 << data_size;
end_of_information = clear + 1;
available = clear + 2;
old_code = nullCode;
code_size = data_size + 1;
code_mask = (1 << code_size) - 1;
for (code = 0; code < clear; code++) {
prefix[code] = 0;
suffix[code] = code;
}
var datum, bits, count, first, top, pi, bi;
datum = bits = count = first = top = pi = bi = 0;
for (i = 0; i < npix; ) {
if (top === 0) {
if (bits < code_size) {
datum += data[bi] << bits;
bits += 8;
bi++;
continue;
}
code = datum & code_mask;
datum >>= code_size;
bits -= code_size;
if (code > available || code == end_of_information) {
break;
}
if (code == clear) {
code_size = data_size + 1;
code_mask = (1 << code_size) - 1;
available = clear + 2;
old_code = nullCode;
continue;
}
if (old_code == nullCode) {
pixelStack[top++] = suffix[code];
old_code = code;
first = code;
continue;
}
in_code = code;
if (code == available) {
pixelStack[top++] = first;
code = old_code;
}
while (code > clear) {
pixelStack[top++] = suffix[code];
code = prefix[code];
}
first = suffix[code] & 255;
pixelStack[top++] = first;
if (available < MAX_STACK_SIZE) {
prefix[available] = old_code;
suffix[available] = first;
available++;
if ((available & code_mask) === 0 && available < MAX_STACK_SIZE) {
code_size++;
code_mask += available;
}
}
old_code = in_code;
}
top--;
dstPixels[pi++] = pixelStack[top];
i++;
}
for (i = pi; i < npix; i++) {
dstPixels[i] = 0;
}
return dstPixels;
};
exports.lzw = lzw;
}
});
// ../node_modules/gifuct-js/lib/index.js
var require_index = __commonJS({
"../node_modules/gifuct-js/lib/index.js"(exports) {
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.decompressFrames = exports.decompressFrame = exports.parseGIF = void 0;
var _gif = _interopRequireDefault(require_gif());
var _jsBinarySchemaParser = require_lib();
var _uint = require_uint8();
var _deinterlace = require_deinterlace();
var _lzw = require_lzw();
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { "default": obj };
}
var parseGIF = function parseGIF2(arrayBuffer) {
var byteData = new Uint8Array(arrayBuffer);
return (0, _jsBinarySchemaParser.parse)((0, _uint.buildStream)(byteData), _gif["default"]);
};
exports.parseGIF = parseGIF;
var generatePatch = function generatePatch2(image) {
var totalPixels = image.pixels.length;
var patchData = new Uint8ClampedArray(totalPixels * 4);
for (var i = 0; i < totalPixels; i++) {
var pos = i * 4;
var colorIndex = image.pixels[i];
var color = image.colorTable[colorIndex] || [0, 0, 0];
patchData[pos] = color[0];
patchData[pos + 1] = color[1];
patchData[pos + 2] = color[2];
patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0;
}
return patchData;
};
var decompressFrame = function decompressFrame2(frame, gct, buildImagePatch) {
if (!frame.image) {
console.warn("gif frame does not have associated image.");
return;
}
var image = frame.image;
var totalPixels = image.descriptor.width * image.descriptor.height;
var pixels = (0, _lzw.lzw)(image.data.minCodeSize, image.data.blocks, totalPixels);
if (image.descriptor.lct.interlaced) {
pixels = (0, _deinterlace.deinterlace)(pixels, image.descriptor.width);
}
var resultImage = {
pixels,
dims: {
top: frame.image.descriptor.top,
left: frame.image.descriptor.left,
width: frame.image.descriptor.width,
height: frame.image.descriptor.height
}
};
if (image.descriptor.lct && image.descriptor.lct.exists) {
resultImage.colorTable = image.lct;
} else {
resultImage.colorTable = gct;
}
if (frame.gce) {
resultImage.delay = (frame.gce.delay || 10) * 10;
resultImage.disposalType = frame.gce.extras.disposal;
if (frame.gce.extras.transparentColorGiven) {
resultImage.transparentIndex = frame.gce.transparentColorIndex;
}
}
if (buildImagePatch) {
resultImage.patch = generatePatch(resultImage);
}
return resultImage;
};
exports.decompressFrame = decompressFrame;
var decompressFrames = function decompressFrames2(parsedGif, buildImagePatches) {
return parsedGif.frames.filter(function(f) {
return f.image;
}).map(function(f) {
return decompressFrame(f, parsedGif.gct, buildImagePatches);
});
};
exports.decompressFrames = decompressFrames;
}
});
return require_index();
})();

View file

@ -0,0 +1,61 @@
// icons.js - Centralized SVG definitions
const ICONS = {
_create: function(content, defaultProps, customProps = {}) {
let p = { ...defaultProps, ...customProps };
if (customProps.width && !customProps.height) p.height = customProps.width;
let strokeAttrs = p.strokeWidth ? ` stroke-width="${p.strokeWidth}" stroke-linecap="${p.strokeLinecap}" stroke-linejoin="${p.strokeLinejoin}"` : "";
let classAttr = p.className ? ` class="${p.className}"` : "";
let styleAttr = p.style ? ` style="${p.style}"` : "";
return `<svg xmlns="http://www.w3.org/2000/svg" width="${p.width}" height="${p.height}" viewBox="${p.viewBox}" fill="${p.fill}" stroke="${p.stroke}"${strokeAttrs}${classAttr}${styleAttr}>${content}</svg>`;
},
_material: function(content, props) {
return this._create(content, { viewBox: "0 -960 960 960", width: "1.4rem", height: "1.4rem", fill: "var(--text-color)", stroke: "none" }, props);
},
_feather: function(content, props) {
return this._create(content, { viewBox: "0 0 24 24", width: "1.4rem", height: "1.4rem", fill: "none", stroke: "var(--text-color)", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, props);
},
addChat: (props) => ICONS._material(`<path d="M120-160v-600q0-33 23.5-56.5T200-840h480q33 0 56.5 23.5T760-760v203q-10-2-20-2.5t-20-.5q-10 0-20 .5t-20 2.5v-203H200v400h283q-2 10-2.5 20t-.5 20q0 10 .5 20t2.5 20H240L120-160Zm160-440h320v-80H280v80Zm0 160h200v-80H280v80Zm400 280v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-360v-400 400Z"/>`, props),
createGroup: (props) => ICONS._material(`<path d="M500-482q29-32 44.5-73t15.5-85q0-44-15.5-85T500-798q60 8 100 53t40 105q0 60-40 105t-100 53Zm220 322v-120q0-36-16-68.5T662-406q51 18 94.5 46.5T800-280v120h-80Zm80-280v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80Zm-593-87q-47-47-47-113t47-113q47-47 113-47t113 47q47 47 47 113t-47 113q-47 47-113 47t-113-47ZM0-160v-112q0-34 17.5-62.5T64-378q62-31 126-46.5T320-440q66 0 130 15.5T576-378q29 15 46.5 43.5T640-272v112H0Zm320-400q33 0 56.5-23.5T400-640q0-33-23.5-56.5T320-720q-33 0-56.5 23.5T240-640q0 33 23.5 56.5T320-560ZM80-240h480v-32q0-11-5.5-20T540-306q-54-27-109-40.5T320-360q-56 0-111 13.5T100-306q-9 5-14.5 14T80-272v32Zm240-400Zm0 400Z"/>`, props),
createSpace: (props) => ICONS._material(`<path d="M80-120v-720h400v160h400v320h-80v-240H480v80h80v80h-80v80h80v80h-80v80h160v80H80Zm80-80h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm160 480h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80ZM800-40v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80ZM640-440v-80h80v80h-80Zm0 160v-80h80v80h-80Z"/>`, props),
joinSpace: (props) => ICONS._material(`<path d="M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm200 160v-80h160q50 0 85-35t35-85q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 83-58.5 141.5T680-280H520Z"/>`, props),
invites: (props) => ICONS._material(`<path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/>`, props),
notifications: (props) => ICONS._material(`<path d="M160-200v-80h80v-280q0-83 50-147.5T420-792v-28q0-25 17.5-42.5T480-880q25 0 42.5 17.5T540-820v28q80 20 130 84.5T720-560v280h80v80H160Zm320-300Zm0 420q-33 0-56.5-23.5T400-160h160q0 33-23.5 56.5T480-80ZM320-280h320v-280q0-66-47-113t-113-47q-66 0-113 47t-47 113v280Z"/>`, props),
close: (props) => ICONS._material(`<path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/>`, props),
check: (props) => ICONS._material(`<path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z"/>`, props),
delete: (props) => ICONS._material(`<path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/>`, props),
menu: (props) => ICONS._material(`<path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z"/>`, props),
arrowBack: (props) => ICONS._material(`<path d="m313-440 224 224-57 56-320-320 320-320 57 56-224 224h487v80H313Z"/>`, props),
pin: (props) => ICONS._material(`<path d="m640-480 80 80v80H520v240l-40 40-40-40v-240H240v-80l80-80v-280q0-33 23.5-56.5T400-840h160q33 0 56.5 23.5T640-760v280Z"/>`, props),
call: (props) => ICONS._material(`<path d="M798-120q-125 0-247-54.5T329-329Q229-429 174.5-551T120-798q0-18 12-30t30-12h162q14 0 25 9.5t13 22.5l26 140q2 16-1 27t-11 19l-97 98q20 37 47.5 71.5T387-386q31 31 65 57.5t72 48.5l94-94q9-9 23.5-13.5T670-390l138 28q14 4 23 14.5t9 23.5v162q0 18-12 30t-30 12Z"/>`, props),
account: (props) => ICONS._material(`<path d="M200-200h560v-560H200v560Zm0 80q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm40-160h480L570-480 450-320l-90-120-120 160Zm-40 80v-560 560Z"/>`, props),
settings: (props) => ICONS._material(`<path d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 167-116.5 283.5T480-80Zm0-157q102 0 174-72t72-174q0-102-72-174t-174-72q-102 0-174 72t-72 174q0 102 72 174t174 72Zm0-80q-68 0-116.5-48.5T315-482q0-68 48.5-116.5T480-647q68 0 116.5 48.5T645-482q0 68-48.5 116.5T480-317Zm0-60q43 0 74-31t31-74q0-43-31-74t-74-31q-43 0-74 31t-31 74q0 43 31 74t74 31Zm0-105Z"/>`, props),
profile: (props) => ICONS._material(`<path d="M480-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM160-160v-112q0-34 17.5-62.5T224-378q62-31 126-46.5T480-440q66 0 130 15.5T736-378q29 15 46.5 43.5T800-272v112H160Zm80-80h480v-32q0-11-5.5-20T700-306q-54-27-109-40.5T480-360q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T560-640q0-33-23.5-56.5T480-720q-33 0-56.5 23.5T400-640q0 33 23.5 56.5T480-560Zm0-80Zm0 400Z"/>`, props),
replyAction: (props) => ICONS._material(`<path d="M760-200v-160q0-50-35-85t-85-35H273l144 144-57 56-240-240 240-240 57 56-144 144h367q83 0 141.5 58.5T840-360v160h-80Z"/>`, props),
react: (props) => ICONS._material(`<path d="M480-260q70 0 126.5-40.5T682-400H278q19 59 75.5 99.5T480-260Zm-160-220q25 0 42.5-17.5T380-540q0-25-17.5-42.5T320-600q-25 0-42.5 17.5T260-540q0 25 17.5 42.5T320-480Zm320 0q25 0 42.5-17.5T700-540q0-25-17.5-42.5T640-600q-25 0-42.5 17.5T580-540q0 25 17.5 42.5T640-480ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/>`, props),
settingsProfile: (props) => ICONS._material(`<path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/>`, props),
download: (props) => ICONS._feather(`<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>`, props),
camera: (props) => ICONS._feather(`<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path><circle cx="12" cy="13" r="4"></circle>`, props),
image: (props) => ICONS._feather(`<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline>`, props),
fileText: (props) => ICONS._feather(`<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>`, props),
poll: (props) => ICONS._feather(`<line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line>`, props),
send: (props) => ICONS._create(`<path stroke="var(--main-bg-color)" stroke-linecap="round" stroke-linejoin="round" d="M 4.65 8 l -1.875 5.625 l 11.25 -5.625 L 2.775 2.375 l 1.875 5.625 z m 0 0 h 3.75" stroke-width="1.1"></path>`, { viewBox: "0 0 16 16", width: "1.5rem", height: "1.5rem", fill: "none", stroke: "none" }, props),
plus: (props) => ICONS._feather(`<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>`, props),
chevronDown: (props) => ICONS._feather(`<polyline points="6 9 12 15 18 9"></polyline>`, props),
lockOpen: (props) => ICONS._feather(`<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path>`, props),
cornerUpLeft: (props) => ICONS._feather(`<polyline points="9 17 4 12 9 7"></polyline><path d="M20 18v-2a4 4 0 0 0-4-4H4"></path>`, props),
spinner: (props) => ICONS._create(`<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>`, { viewBox: "0 0 50 50", width: "24px", height: "24px", fill: "none", stroke: "none", className: "spinner" }, props),
video: (props) => ICONS._feather(`<polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>`, props),
file: (props) => ICONS._feather(`<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>`, props),
home: (props) => ICONS._create(`<path d="M1 6V15H6V11C6 9.89543 6.89543 9 8 9C9.10457 9 10 9.89543 10 11V15H15V6L8 0L1 6Z" stroke="currentColor" stroke-width="1.28" stroke-linejoin="round" stroke-linecap="round" transform="translate(0 0.5)"/>`, { viewBox: "-1 -1 18 18", width: "24", height: "24", fill: "none", stroke: "none" }, props),
inbox: (props) => ICONS._material(`<path d="M216-144q-29 0-50.5-21.5T144-216v-528q0-29.7 21.5-50.85Q187-816 216-816h528q29.7 0 50.85 21.15Q816-773.7 816-744v528q0 29-21.15 50.5T744-144H216Zm0-72h528v-144H632q-23 43-63.5 69.5T480-264q-49 0-89.5-26T328-360H216v144Zm332-148q28-28 28-68h168v-312H216v312h168q0 40 28 68t68 28q40 0 68-28ZM216-216h528-528Z"/>`, props),
avatar: function(initials, color, size) {
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}"><rect width="100%" height="100%" fill="${color}" /><text x="50%" y="50%" fill="white" font-family="Nunito" font-size="${size * 0.45}" text-anchor="middle" dy=".35em">${initials}</text></svg>`;
}
};

View file

@ -6,6 +6,13 @@
<title>Miarven</title>
<link rel="stylesheet" href="style.css">
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<script src="crypto-pq.js"></script>
<!-- debug
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>eruda.init();</script> -->
<script src="icons.js"></script>
</head>
<body>
<loading>
@ -20,20 +27,14 @@
<indicator class="active">
</indicator>
<button class="icon-button" onclick="gotoHome()">
<svg viewBox="-1 -1 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 6V15H6V11C6 9.89543 6.89543 9 8 9C9.10457 9 10 9.89543 10 11V15H15V6L8 0L1 6Z" stroke="var(--text-color)" stroke-width="1.28" stroke-linejoin="round" stroke-linecap="round" transform="translate(0 0.5)"/>
</svg>
<script>document.write(ICONS.home())</script>
</button>
</sidebarelement>
<sidebarelement id="sidebar-add">
<indicator>
</indicator>
<button class="icon-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="var(--text-color)"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
<script>document.write(ICONS.plus())</script>
</button>
</sidebarelement>
<hr>
@ -42,13 +43,13 @@
<indicator>
</indicator>
<button class="icon-button" onclick="gotoInbox()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M216-144q-29 0-50.5-21.5T144-216v-528q0-29.7 21.5-50.85Q187-816 216-816h528q29.7 0 50.85 21.15Q816-773.7 816-744v528q0 29-21.15 50.5T744-144H216Zm0-72h528v-144H632q-23 43-63.5 69.5T480-264q-49 0-89.5-26T328-360H216v144Zm332-148q28-28 28-68h168v-312H216v312h168q0 40 28 68t68 28q40 0 68-28ZM216-216h528-528Z"/></svg>
<script>document.write(ICONS.inbox())</script>
</button>
</sidebarelement>
<sidebarelement id="sidebar-profile">
<indicator>
</indicator>
<button class="icon-button" onclick="gotoSideProfilePopup()">
<button class="icon-button">
<img id="sidebar-pfp">
</button>
</sidebarelement>
@ -78,14 +79,42 @@
<div id="fixed-context-menu" class="context-menu">
</div>
<div id="profile-popup-container" class="profile-popup-container" style="display: none;">
<div class="profile-popup-bg" onclick="closeProfile()"></div>
<div class="profile-popup-content">
<button class="profile-popup-close-btn" onclick="closeProfile()">
<script>document.write(ICONS.close())</script>
</button>
<div id="profile-popup-inner"></div>
</div>
</div>
<div id="cropper-popup-container" class="profile-popup-container" style="z-index: 9999;">
<div class="profile-popup-bg" onclick="closeCropper()"></div>
<div class="profile-popup-content" style="max-width: 40rem; padding: 2rem; display: flex; flex-direction: column; align-items: center; gap: 1rem; height: auto;">
<h2><blah>title.crop.image</blah></h2>
<div id="cropper-viewport-container" style="position: relative; overflow: hidden; background: #000; width: 100%; max-width: 30rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255,255,255,0.2);">
<img id="cropper-image" src="" style="position: absolute; transform-origin: 0 0; cursor: move;">
</div>
<div style="display: flex; align-items: center; gap: 1rem; width: 100%; max-width: 30rem;">
<span><blah>title.zoom</blah></span>
<input type="range" id="cropper-zoom" min="0.1" max="5" step="0.01" value="1" style="flex: 1;" oninput="updateCropperZoom()">
</div>
<div style="display: flex; gap: 1rem; margin-top: 1rem; width: 100%; max-width: 30rem;">
<button class="submit-button blah" style="background: rgba(255,255,255,0.1); color: var(--text-color); flex: 1;" onclick="closeCropper()">action.cancel</button>
<button class="submit-button blah" style="flex: 1;" onclick="confirmCropper()">action.save</button>
</div>
</div>
</div>
</body>
</html>
<script src="screens.js"></script>
<script>
async function start() {
updateLoadingStatus("loading.loading");
try {
gotoHome();
if (host != null)
{
await updateProtocolAndUrl(host);
@ -94,13 +123,21 @@
{
await updateProtocolAndUrl(window.location.hostname);
}
sidebarPfp.src = await getAvatarUrl(username);
let localIdRes = await fetchAsync(`${url}/nametoid?u=${username}`);
let localId = localIdRes.trim();
sidebarPfp.src = await getAvatarUrl(localId, `${username}:${host}`);
showAction("Authenticating...", "startauth");
showAction("action.auth", "startauth");
let res = await Auth(username, password);
clearAction("startauth");
if (res.startsWith("success:")) {
await refreshDms();
try {
await ensureUserKeys();
} catch (e) {
//if fails continue loading encryption may be broken
console.error(e);
}
gotoHome();
} else {
showBlahNotification("error:auth.failed.redirect.to.login");
await delay(2000);
@ -116,26 +153,39 @@
}
}
async function refreshDms() {
async function refreshDms(waittime = 0, showLoading = true) {
try {
showAction("Refreshing dms...", "dmrefresh");
let res = await fetchEncrypted("user/dm/list", "");
console.log(res);
clearAction("dmrefresh");
if (showLoading) showAction("action.dm.fetch", "dmrefresh");
await delay(waittime);
if (window.cachedDms && typeof renderDms === 'function') {
await renderDms(window.cachedDms);
}
let res = await fetchEncrypted("user/dm/list");
window.cachedDms = res;
if (typeof renderDms === 'function') {
await renderDms(res);
}
if (showLoading) clearAction("dmrefresh");
}
catch (e) {
clearAction("dmrefresh");
if (showLoading) clearAction("dmrefresh");
showBlahNotification("error:dm.refresh.failed");
}
}
async function addDm() {
try {
showAction("Adding...", "dmadd");
let res = await fetchEncrypted("user/dm/invite", document.getElementById("addchat-username").value);
console.log(res);
showAction("action.dm.adding", "dmadd");
let username = document.getElementById("addchat-username").value;
let idRes = await fetchAsync(`${url}/nametoid?u=${username}`);
if (idRes.startsWith("error") || idRes.trim() === "0" || idRes.trim() === "") {
clearAction("dmadd");
showBlahNotification("error:user.not.found");
return;
}
let targetId = idRes.trim();
let res = await fetchEncrypted("user/dm/invite", targetId);
clearAction("dmadd");
showBlahNotification(res);
}
@ -150,5 +200,6 @@
}
</script>
<script src="gifuct.js"></script>
<script src="main.js"></script>
<script src="userscript.js"></script>

View file

@ -6,6 +6,7 @@
<title>Miarven - Login</title>
<link rel="stylesheet" href="../style.css">
<link rel="icon" type="image/svg+xml" href="../favicon.svg">
<script src="../crypto-pq.js"></script>
<style>
html {
font-size: max(17px, calc(100vw / 100));
@ -267,10 +268,9 @@
</main>
</body>
</html>
<script src="../main.js"></script>
<script>
const container = document.getElementById('auth-container');
const authContainer = document.getElementById('auth-container');
const toRegister = document.getElementById('to-register');
@ -306,31 +306,35 @@
toRegister.addEventListener('click', () => {
container.className = 'auth-container show-register';
authContainer.className = 'auth-container show-register';
});
toLogin.addEventListener('click', () => {
container.className = 'auth-container';
authContainer.className = 'auth-container';
});
backToReg.addEventListener('click', () => {
container.className = 'auth-container show-register';
authContainer.className = 'auth-container show-register';
});
formLogin.addEventListener('submit', async (e) => {
e.preventDefault();
try {
await updateProtocolAndUrl(loginHost.value);
let res = await Auth(loginUsername.value, loginPassword.value);
if (res.startsWith("success:"))
{
showBlahNotification(res);
let parts = res.split("|");
showBlahNotification(parts[0]);
await delay(800);
localStorage.setItem("username", loginUsername.value);
localStorage.setItem("password", loginPassword.value);
localStorage.setItem("host", loginHost.value);
if (parts.length > 1) {
localStorage.setItem("id", parts[1]);
}
await loadingFadeIn();
@ -344,7 +348,7 @@
} catch (error) {
console.log(error);
showBlahNotification("error:something.wrong.mayb.pass");
container.className = 'auth-container';
authContainer.className = 'auth-container';
}
});
@ -358,27 +362,31 @@
if (!registerUsername.value || registerUsername.value.trim() === '') {
showBlahNotification("error:username.cant.empty");
container.className = 'auth-container show-register';
authContainer.className = 'auth-container show-register';
return;
}
if (!registerPassword.value || registerPassword.value.trim() === '') {
showBlahNotification("error:password.cant.empty");
container.className = 'auth-container show-register';
authContainer.className = 'auth-container show-register';
return;
}
if (registerPassword.value != registerPasswordConfirm.value) {
showBlahNotification("error:passwords.not.match");
container.className = 'auth-container show-register';
authContainer.className = 'auth-container show-register';
return;
}
await updateProtocolAndUrl(registerHost.value);
let dataarray = keyDataFromServerJson(await fetchAsync(`${url}/createaccount?step=init`));
var sharedkey = await calcCommunicationKeyClient(dataarray[0], dataarray[1], dataarray[2]);
sharedpvkey = sharedkey[1];
createId = dataarray[3];
var sharedkey = await calcHybridSharedKeyClient(dataarray[0], dataarray[1]);
sharedpvkey = sharedkey[2];
createId = dataarray[2];
const captchaimage = await fetch(`${url}/createaccount?step=register`, {
method: 'POST',
body: JSON.stringify({
pubClient: sharedkey[0],
pubX25519: sharedkey[0],
ciphertextMlKem: sharedkey[1],
idKey: createId,
username: await encrypt(registerUsername.value, sharedpvkey),
password: await encrypt(await hashSHA3_512(registerPassword.value), sharedpvkey)
@ -393,20 +401,20 @@
}
const imageObjectURL = URL.createObjectURL(imageBlob);
document.getElementById('captcha-image').src = imageObjectURL;
container.className = 'auth-container show-captcha';
authContainer.className = 'auth-container show-captcha';
} catch (error) {
console.log(error);
if (res.length > 64 || res.length <= 1) {
throw new Error();
}
showBlahNotification(res);
container.className = 'auth-container show-register';
authContainer.className = 'auth-container show-register';
}
} catch (error) {
console.log(error);
showBlahNotification("error:something.wrong");
container.className = 'auth-container show-register';
authContainer.className = 'auth-container show-register';
}
});
@ -428,6 +436,12 @@
if (res.startsWith("success")) {
showBlahNotification(res);
await delay(1000);
let resolvedId = await fetchAsync(`${url}/nametoid?u=${registerUsername.value}`);
if (resolvedId && !resolvedId.startsWith("error:")) {
localStorage.setItem("id", resolvedId.split(":")[0]);
}
localStorage.setItem("username", registerUsername.value);
localStorage.setItem("password", registerPassword.value);
localStorage.setItem("host", registerHost.value);
@ -436,12 +450,12 @@
} else {
showBlahNotification(res);
container.className = 'auth-container show-register';
authContainer.className = 'auth-container show-register';
}
}
} catch (error) {
showBlahNotification("error:something.wrong");
container.className = 'auth-container show-register';
authContainer.className = 'auth-container show-register';
}
});
@ -492,4 +506,5 @@
}
</script>
<script src="../main.js"></script>
<script src="../userscript.js"></script>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,807 @@
// (c) Dean McNamee <dean@gmail.com>, 2013.
//
// https://github.com/deanm/omggif
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// omggif is a JavaScript implementation of a GIF 89a encoder and decoder,
// including animation and compression. It does not rely on any specific
// underlying system, so should run in the browser, Node, or Plask.
"use strict";
function GifWriter(buf, width, height, gopts) {
var p = 0;
var gopts = gopts === undefined ? { } : gopts;
var loop_count = gopts.loop === undefined ? null : gopts.loop;
var global_palette = gopts.palette === undefined ? null : gopts.palette;
if (width <= 0 || height <= 0 || width > 65535 || height > 65535)
throw new Error("Width/Height invalid.");
function check_palette_and_num_colors(palette) {
var num_colors = palette.length;
if (num_colors < 2 || num_colors > 256 || num_colors & (num_colors-1)) {
throw new Error(
"Invalid code/color length, must be power of 2 and 2 .. 256.");
}
return num_colors;
}
// - Header.
buf[p++] = 0x47; buf[p++] = 0x49; buf[p++] = 0x46; // GIF
buf[p++] = 0x38; buf[p++] = 0x39; buf[p++] = 0x61; // 89a
// Handling of Global Color Table (palette) and background index.
var gp_num_colors_pow2 = 0;
var background = 0;
if (global_palette !== null) {
var gp_num_colors = check_palette_and_num_colors(global_palette);
while (gp_num_colors >>= 1) ++gp_num_colors_pow2;
gp_num_colors = 1 << gp_num_colors_pow2;
--gp_num_colors_pow2;
if (gopts.background !== undefined) {
background = gopts.background;
if (background >= gp_num_colors)
throw new Error("Background index out of range.");
// The GIF spec states that a background index of 0 should be ignored, so
// this is probably a mistake and you really want to set it to another
// slot in the palette. But actually in the end most browsers, etc end
// up ignoring this almost completely (including for dispose background).
if (background === 0)
throw new Error("Background index explicitly passed as 0.");
}
}
// - Logical Screen Descriptor.
// NOTE(deanm): w/h apparently ignored by implementations, but set anyway.
buf[p++] = width & 0xff; buf[p++] = width >> 8 & 0xff;
buf[p++] = height & 0xff; buf[p++] = height >> 8 & 0xff;
// NOTE: Indicates 0-bpp original color resolution (unused?).
buf[p++] = (global_palette !== null ? 0x80 : 0) | // Global Color Table Flag.
gp_num_colors_pow2; // NOTE: No sort flag (unused?).
buf[p++] = background; // Background Color Index.
buf[p++] = 0; // Pixel aspect ratio (unused?).
// - Global Color Table
if (global_palette !== null) {
for (var i = 0, il = global_palette.length; i < il; ++i) {
var rgb = global_palette[i];
buf[p++] = rgb >> 16 & 0xff;
buf[p++] = rgb >> 8 & 0xff;
buf[p++] = rgb & 0xff;
}
}
if (loop_count !== null) { // Netscape block for looping.
if (loop_count < 0 || loop_count > 65535)
throw new Error("Loop count invalid.")
// Extension code, label, and length.
buf[p++] = 0x21; buf[p++] = 0xff; buf[p++] = 0x0b;
// NETSCAPE2.0
buf[p++] = 0x4e; buf[p++] = 0x45; buf[p++] = 0x54; buf[p++] = 0x53;
buf[p++] = 0x43; buf[p++] = 0x41; buf[p++] = 0x50; buf[p++] = 0x45;
buf[p++] = 0x32; buf[p++] = 0x2e; buf[p++] = 0x30;
// Sub-block
buf[p++] = 0x03; buf[p++] = 0x01;
buf[p++] = loop_count & 0xff; buf[p++] = loop_count >> 8 & 0xff;
buf[p++] = 0x00; // Terminator.
}
var ended = false;
this.addFrame = function(x, y, w, h, indexed_pixels, opts) {
if (ended === true) { --p; ended = false; } // Un-end.
opts = opts === undefined ? { } : opts;
// TODO(deanm): Bounds check x, y. Do they need to be within the virtual
// canvas width/height, I imagine?
if (x < 0 || y < 0 || x > 65535 || y > 65535)
throw new Error("x/y invalid.")
if (w <= 0 || h <= 0 || w > 65535 || h > 65535)
throw new Error("Width/Height invalid.")
if (indexed_pixels.length < w * h)
throw new Error("Not enough pixels for the frame size.");
var using_local_palette = true;
var palette = opts.palette;
if (palette === undefined || palette === null) {
using_local_palette = false;
palette = global_palette;
}
if (palette === undefined || palette === null)
throw new Error("Must supply either a local or global palette.");
var num_colors = check_palette_and_num_colors(palette);
// Compute the min_code_size (power of 2), destroying num_colors.
var min_code_size = 0;
while (num_colors >>= 1) ++min_code_size;
num_colors = 1 << min_code_size; // Now we can easily get it back.
var delay = opts.delay === undefined ? 0 : opts.delay;
// From the spec:
// 0 - No disposal specified. The decoder is
// not required to take any action.
// 1 - Do not dispose. The graphic is to be left
// in place.
// 2 - Restore to background color. The area used by the
// graphic must be restored to the background color.
// 3 - Restore to previous. The decoder is required to
// restore the area overwritten by the graphic with
// what was there prior to rendering the graphic.
// 4-7 - To be defined.
// NOTE(deanm): Dispose background doesn't really work, apparently most
// browsers ignore the background palette index and clear to transparency.
var disposal = opts.disposal === undefined ? 0 : opts.disposal;
if (disposal < 0 || disposal > 3) // 4-7 is reserved.
throw new Error("Disposal out of range.");
var use_transparency = false;
var transparent_index = 0;
if (opts.transparent !== undefined && opts.transparent !== null) {
use_transparency = true;
transparent_index = opts.transparent;
if (transparent_index < 0 || transparent_index >= num_colors)
throw new Error("Transparent color index.");
}
if (disposal !== 0 || use_transparency || delay !== 0) {
// - Graphics Control Extension
buf[p++] = 0x21; buf[p++] = 0xf9; // Extension / Label.
buf[p++] = 4; // Byte size.
buf[p++] = disposal << 2 | (use_transparency === true ? 1 : 0);
buf[p++] = delay & 0xff; buf[p++] = delay >> 8 & 0xff;
buf[p++] = transparent_index; // Transparent color index.
buf[p++] = 0; // Block Terminator.
}
// - Image Descriptor
buf[p++] = 0x2c; // Image Seperator.
buf[p++] = x & 0xff; buf[p++] = x >> 8 & 0xff; // Left.
buf[p++] = y & 0xff; buf[p++] = y >> 8 & 0xff; // Top.
buf[p++] = w & 0xff; buf[p++] = w >> 8 & 0xff;
buf[p++] = h & 0xff; buf[p++] = h >> 8 & 0xff;
// NOTE: No sort flag (unused?).
// TODO(deanm): Support interlace.
buf[p++] = using_local_palette === true ? (0x80 | (min_code_size-1)) : 0;
// - Local Color Table
if (using_local_palette === true) {
for (var i = 0, il = palette.length; i < il; ++i) {
var rgb = palette[i];
buf[p++] = rgb >> 16 & 0xff;
buf[p++] = rgb >> 8 & 0xff;
buf[p++] = rgb & 0xff;
}
}
p = GifWriterOutputLZWCodeStream(
buf, p, min_code_size < 2 ? 2 : min_code_size, indexed_pixels);
return p;
};
this.end = function() {
if (ended === false) {
buf[p++] = 0x3b; // Trailer.
ended = true;
}
return p;
};
this.getOutputBuffer = function() { return buf; };
this.setOutputBuffer = function(v) { buf = v; };
this.getOutputBufferPosition = function() { return p; };
this.setOutputBufferPosition = function(v) { p = v; };
}
// Main compression routine, palette indexes -> LZW code stream.
// |index_stream| must have at least one entry.
function GifWriterOutputLZWCodeStream(buf, p, min_code_size, index_stream) {
buf[p++] = min_code_size;
var cur_subblock = p++; // Pointing at the length field.
var clear_code = 1 << min_code_size;
var code_mask = clear_code - 1;
var eoi_code = clear_code + 1;
var next_code = eoi_code + 1;
var cur_code_size = min_code_size + 1; // Number of bits per code.
var cur_shift = 0;
// We have at most 12-bit codes, so we should have to hold a max of 19
// bits here (and then we would write out).
var cur = 0;
function emit_bytes_to_buffer(bit_block_size) {
while (cur_shift >= bit_block_size) {
buf[p++] = cur & 0xff;
cur >>= 8; cur_shift -= 8;
if (p === cur_subblock + 256) { // Finished a subblock.
buf[cur_subblock] = 255;
cur_subblock = p++;
}
}
}
function emit_code(c) {
cur |= c << cur_shift;
cur_shift += cur_code_size;
emit_bytes_to_buffer(8);
}
// I am not an expert on the topic, and I don't want to write a thesis.
// However, it is good to outline here the basic algorithm and the few data
// structures and optimizations here that make this implementation fast.
// The basic idea behind LZW is to build a table of previously seen runs
// addressed by a short id (herein called output code). All data is
// referenced by a code, which represents one or more values from the
// original input stream. All input bytes can be referenced as the same
// value as an output code. So if you didn't want any compression, you
// could more or less just output the original bytes as codes (there are
// some details to this, but it is the idea). In order to achieve
// compression, values greater then the input range (codes can be up to
// 12-bit while input only 8-bit) represent a sequence of previously seen
// inputs. The decompressor is able to build the same mapping while
// decoding, so there is always a shared common knowledge between the
// encoding and decoder, which is also important for "timing" aspects like
// how to handle variable bit width code encoding.
//
// One obvious but very important consequence of the table system is there
// is always a unique id (at most 12-bits) to map the runs. 'A' might be
// 4, then 'AA' might be 10, 'AAA' 11, 'AAAA' 12, etc. This relationship
// can be used for an effecient lookup strategy for the code mapping. We
// need to know if a run has been seen before, and be able to map that run
// to the output code. Since we start with known unique ids (input bytes),
// and then from those build more unique ids (table entries), we can
// continue this chain (almost like a linked list) to always have small
// integer values that represent the current byte chains in the encoder.
// This means instead of tracking the input bytes (AAAABCD) to know our
// current state, we can track the table entry for AAAABC (it is guaranteed
// to exist by the nature of the algorithm) and the next character D.
// Therefor the tuple of (table_entry, byte) is guaranteed to also be
// unique. This allows us to create a simple lookup key for mapping input
// sequences to codes (table indices) without having to store or search
// any of the code sequences. So if 'AAAA' has a table entry of 12, the
// tuple of ('AAAA', K) for any input byte K will be unique, and can be our
// key. This leads to a integer value at most 20-bits, which can always
// fit in an SMI value and be used as a fast sparse array / object key.
// Output code for the current contents of the index buffer.
var ib_code = index_stream[0] & code_mask; // Load first input index.
var code_table = { }; // Key'd on our 20-bit "tuple".
emit_code(clear_code); // Spec says first code should be a clear code.
// First index already loaded, process the rest of the stream.
for (var i = 1, il = index_stream.length; i < il; ++i) {
var k = index_stream[i] & code_mask;
var cur_key = ib_code << 8 | k; // (prev, k) unique tuple.
var cur_code = code_table[cur_key]; // buffer + k.
// Check if we have to create a new code table entry.
if (cur_code === undefined) { // We don't have buffer + k.
// Emit index buffer (without k).
// This is an inline version of emit_code, because this is the core
// writing routine of the compressor (and V8 cannot inline emit_code
// because it is a closure here in a different context). Additionally
// we can call emit_byte_to_buffer less often, because we can have
// 30-bits (from our 31-bit signed SMI), and we know our codes will only
// be 12-bits, so can safely have 18-bits there without overflow.
// emit_code(ib_code);
cur |= ib_code << cur_shift;
cur_shift += cur_code_size;
while (cur_shift >= 8) {
buf[p++] = cur & 0xff;
cur >>= 8; cur_shift -= 8;
if (p === cur_subblock + 256) { // Finished a subblock.
buf[cur_subblock] = 255;
cur_subblock = p++;
}
}
if (next_code === 4096) { // Table full, need a clear.
emit_code(clear_code);
next_code = eoi_code + 1;
cur_code_size = min_code_size + 1;
code_table = { };
} else { // Table not full, insert a new entry.
// Increase our variable bit code sizes if necessary. This is a bit
// tricky as it is based on "timing" between the encoding and
// decoder. From the encoders perspective this should happen after
// we've already emitted the index buffer and are about to create the
// first table entry that would overflow our current code bit size.
if (next_code >= (1 << cur_code_size)) ++cur_code_size;
code_table[cur_key] = next_code++; // Insert into code table.
}
ib_code = k; // Index buffer to single input k.
} else {
ib_code = cur_code; // Index buffer to sequence in code table.
}
}
emit_code(ib_code); // There will still be something in the index buffer.
emit_code(eoi_code); // End Of Information.
// Flush / finalize the sub-blocks stream to the buffer.
emit_bytes_to_buffer(1);
// Finish the sub-blocks, writing out any unfinished lengths and
// terminating with a sub-block of length 0. If we have already started
// but not yet used a sub-block it can just become the terminator.
if (cur_subblock + 1 === p) { // Started but unused.
buf[cur_subblock] = 0;
} else { // Started and used, write length and additional terminator block.
buf[cur_subblock] = p - cur_subblock - 1;
buf[p++] = 0;
}
return p;
}
function GifReader(buf) {
var p = 0;
// - Header (GIF87a or GIF89a).
if (buf[p++] !== 0x47 || buf[p++] !== 0x49 || buf[p++] !== 0x46 ||
buf[p++] !== 0x38 || (buf[p++]+1 & 0xfd) !== 0x38 || buf[p++] !== 0x61) {
throw new Error("Invalid GIF 87a/89a header.");
}
// - Logical Screen Descriptor.
var width = buf[p++] | buf[p++] << 8;
var height = buf[p++] | buf[p++] << 8;
var pf0 = buf[p++]; // <Packed Fields>.
var global_palette_flag = pf0 >> 7;
var num_global_colors_pow2 = pf0 & 0x7;
var num_global_colors = 1 << (num_global_colors_pow2 + 1);
var background = buf[p++];
buf[p++]; // Pixel aspect ratio (unused?).
var global_palette_offset = null;
var global_palette_size = null;
if (global_palette_flag) {
global_palette_offset = p;
global_palette_size = num_global_colors;
p += num_global_colors * 3; // Seek past palette.
}
var no_eof = true;
var frames = [ ];
var delay = 0;
var transparent_index = null;
var disposal = 0; // 0 - No disposal specified.
var loop_count = null;
this.width = width;
this.height = height;
while (no_eof && p < buf.length) {
switch (buf[p++]) {
case 0x21: // Graphics Control Extension Block
switch (buf[p++]) {
case 0xff: // Application specific block
// Try if it's a Netscape block (with animation loop counter).
if (buf[p ] !== 0x0b || // 21 FF already read, check block size.
// NETSCAPE2.0
buf[p+1 ] == 0x4e && buf[p+2 ] == 0x45 && buf[p+3 ] == 0x54 &&
buf[p+4 ] == 0x53 && buf[p+5 ] == 0x43 && buf[p+6 ] == 0x41 &&
buf[p+7 ] == 0x50 && buf[p+8 ] == 0x45 && buf[p+9 ] == 0x32 &&
buf[p+10] == 0x2e && buf[p+11] == 0x30 &&
// Sub-block
buf[p+12] == 0x03 && buf[p+13] == 0x01 && buf[p+16] == 0) {
p += 14;
loop_count = buf[p++] | buf[p++] << 8;
p++; // Skip terminator.
} else { // We don't know what it is, just try to get past it.
p += 12;
while (true) { // Seek through subblocks.
var block_size = buf[p++];
// Bad block size (ex: undefined from an out of bounds read).
if (!(block_size >= 0)) throw Error("Invalid block size");
if (block_size === 0) break; // 0 size is terminator
p += block_size;
}
}
break;
case 0xf9: // Graphics Control Extension
if (buf[p++] !== 0x4 || buf[p+4] !== 0)
throw new Error("Invalid graphics extension block.");
var pf1 = buf[p++];
delay = buf[p++] | buf[p++] << 8;
transparent_index = buf[p++];
if ((pf1 & 1) === 0) transparent_index = null;
disposal = pf1 >> 2 & 0x7;
p++; // Skip terminator.
break;
case 0xfe: // Comment Extension.
while (true) { // Seek through subblocks.
var block_size = buf[p++];
// Bad block size (ex: undefined from an out of bounds read).
if (!(block_size >= 0)) throw Error("Invalid block size");
if (block_size === 0) break; // 0 size is terminator
// console.log(buf.slice(p, p+block_size).toString('ascii'));
p += block_size;
}
break;
default:
throw new Error(
"Unknown graphic control label: 0x" + buf[p-1].toString(16));
}
break;
case 0x2c: // Image Descriptor.
var x = buf[p++] | buf[p++] << 8;
var y = buf[p++] | buf[p++] << 8;
var w = buf[p++] | buf[p++] << 8;
var h = buf[p++] | buf[p++] << 8;
var pf2 = buf[p++];
var local_palette_flag = pf2 >> 7;
var interlace_flag = pf2 >> 6 & 1;
var num_local_colors_pow2 = pf2 & 0x7;
var num_local_colors = 1 << (num_local_colors_pow2 + 1);
var palette_offset = global_palette_offset;
var palette_size = global_palette_size;
var has_local_palette = false;
if (local_palette_flag) {
var has_local_palette = true;
palette_offset = p; // Override with local palette.
palette_size = num_local_colors;
p += num_local_colors * 3; // Seek past palette.
}
var data_offset = p;
p++; // codesize
while (true) {
var block_size = buf[p++];
// Bad block size (ex: undefined from an out of bounds read).
if (!(block_size >= 0)) throw Error("Invalid block size");
if (block_size === 0) break; // 0 size is terminator
p += block_size;
}
frames.push({x: x, y: y, width: w, height: h,
has_local_palette: has_local_palette,
palette_offset: palette_offset,
palette_size: palette_size,
data_offset: data_offset,
data_length: p - data_offset,
transparent_index: transparent_index,
interlaced: !!interlace_flag,
delay: delay,
disposal: disposal});
break;
case 0x3b: // Trailer Marker (end of file).
no_eof = false;
break;
default:
throw new Error("Unknown gif block: 0x" + buf[p-1].toString(16));
break;
}
}
this.numFrames = function() {
return frames.length;
};
this.loopCount = function() {
return loop_count;
};
this.frameInfo = function(frame_num) {
if (frame_num < 0 || frame_num >= frames.length)
throw new Error("Frame index out of range.");
return frames[frame_num];
}
this.decodeAndBlitFrameBGRA = function(frame_num, pixels) {
var frame = this.frameInfo(frame_num);
var num_pixels = frame.width * frame.height;
var index_stream = new Uint8Array(num_pixels); // At most 8-bit indices.
GifReaderLZWOutputIndexStream(
buf, frame.data_offset, index_stream, num_pixels);
var palette_offset = frame.palette_offset;
// NOTE(deanm): It seems to be much faster to compare index to 256 than
// to === null. Not sure why, but CompareStub_EQ_STRICT shows up high in
// the profile, not sure if it's related to using a Uint8Array.
var trans = frame.transparent_index;
if (trans === null) trans = 256;
// We are possibly just blitting to a portion of the entire frame.
// That is a subrect within the framerect, so the additional pixels
// must be skipped over after we finished a scanline.
var framewidth = frame.width;
var framestride = width - framewidth;
var xleft = framewidth; // Number of subrect pixels left in scanline.
// Output indicies of the top left and bottom right corners of the subrect.
var opbeg = ((frame.y * width) + frame.x) * 4;
var opend = ((frame.y + frame.height) * width + frame.x) * 4;
var op = opbeg;
var scanstride = framestride * 4;
// Use scanstride to skip past the rows when interlacing. This is skipping
// 7 rows for the first two passes, then 3 then 1.
if (frame.interlaced === true) {
scanstride += width * 4 * 7; // Pass 1.
}
var interlaceskip = 8; // Tracking the row interval in the current pass.
for (var i = 0, il = index_stream.length; i < il; ++i) {
var index = index_stream[i];
if (xleft === 0) { // Beginning of new scan line
op += scanstride;
xleft = framewidth;
if (op >= opend) { // Catch the wrap to switch passes when interlacing.
scanstride = framestride * 4 + width * 4 * (interlaceskip-1);
// interlaceskip / 2 * 4 is interlaceskip << 1.
op = opbeg + (framewidth + framestride) * (interlaceskip << 1);
interlaceskip >>= 1;
}
}
if (index === trans) {
op += 4;
} else {
var r = buf[palette_offset + index * 3];
var g = buf[palette_offset + index * 3 + 1];
var b = buf[palette_offset + index * 3 + 2];
pixels[op++] = b;
pixels[op++] = g;
pixels[op++] = r;
pixels[op++] = 255;
}
--xleft;
}
};
// I will go to copy and paste hell one day...
this.decodeAndBlitFrameRGBA = function(frame_num, pixels) {
var frame = this.frameInfo(frame_num);
var num_pixels = frame.width * frame.height;
var index_stream = new Uint8Array(num_pixels); // At most 8-bit indices.
GifReaderLZWOutputIndexStream(
buf, frame.data_offset, index_stream, num_pixels);
var palette_offset = frame.palette_offset;
// NOTE(deanm): It seems to be much faster to compare index to 256 than
// to === null. Not sure why, but CompareStub_EQ_STRICT shows up high in
// the profile, not sure if it's related to using a Uint8Array.
var trans = frame.transparent_index;
if (trans === null) trans = 256;
// We are possibly just blitting to a portion of the entire frame.
// That is a subrect within the framerect, so the additional pixels
// must be skipped over after we finished a scanline.
var framewidth = frame.width;
var framestride = width - framewidth;
var xleft = framewidth; // Number of subrect pixels left in scanline.
// Output indicies of the top left and bottom right corners of the subrect.
var opbeg = ((frame.y * width) + frame.x) * 4;
var opend = ((frame.y + frame.height) * width + frame.x) * 4;
var op = opbeg;
var scanstride = framestride * 4;
// Use scanstride to skip past the rows when interlacing. This is skipping
// 7 rows for the first two passes, then 3 then 1.
if (frame.interlaced === true) {
scanstride += width * 4 * 7; // Pass 1.
}
var interlaceskip = 8; // Tracking the row interval in the current pass.
for (var i = 0, il = index_stream.length; i < il; ++i) {
var index = index_stream[i];
if (xleft === 0) { // Beginning of new scan line
op += scanstride;
xleft = framewidth;
if (op >= opend) { // Catch the wrap to switch passes when interlacing.
scanstride = framestride * 4 + width * 4 * (interlaceskip-1);
// interlaceskip / 2 * 4 is interlaceskip << 1.
op = opbeg + (framewidth + framestride) * (interlaceskip << 1);
interlaceskip >>= 1;
}
}
if (index === trans) {
op += 4;
} else {
var r = buf[palette_offset + index * 3];
var g = buf[palette_offset + index * 3 + 1];
var b = buf[palette_offset + index * 3 + 2];
pixels[op++] = r;
pixels[op++] = g;
pixels[op++] = b;
pixels[op++] = 255;
}
--xleft;
}
};
}
function GifReaderLZWOutputIndexStream(code_stream, p, output, output_length) {
var min_code_size = code_stream[p++];
var clear_code = 1 << min_code_size;
var eoi_code = clear_code + 1;
var next_code = eoi_code + 1;
var cur_code_size = min_code_size + 1; // Number of bits per code.
// NOTE: This shares the same name as the encoder, but has a different
// meaning here. Here this masks each code coming from the code stream.
var code_mask = (1 << cur_code_size) - 1;
var cur_shift = 0;
var cur = 0;
var op = 0; // Output pointer.
var subblock_size = code_stream[p++];
// TODO(deanm): Would using a TypedArray be any faster? At least it would
// solve the fast mode / backing store uncertainty.
// var code_table = Array(4096);
var code_table = new Int32Array(4096); // Can be signed, we only use 20 bits.
var prev_code = null; // Track code-1.
while (true) {
// Read up to two bytes, making sure we always 12-bits for max sized code.
while (cur_shift < 16) {
if (subblock_size === 0) break; // No more data to be read.
cur |= code_stream[p++] << cur_shift;
cur_shift += 8;
if (subblock_size === 1) { // Never let it get to 0 to hold logic above.
subblock_size = code_stream[p++]; // Next subblock.
} else {
--subblock_size;
}
}
// TODO(deanm): We should never really get here, we should have received
// and EOI.
if (cur_shift < cur_code_size)
break;
var code = cur & code_mask;
cur >>= cur_code_size;
cur_shift -= cur_code_size;
// TODO(deanm): Maybe should check that the first code was a clear code,
// at least this is what you're supposed to do. But actually our encoder
// now doesn't emit a clear code first anyway.
if (code === clear_code) {
// We don't actually have to clear the table. This could be a good idea
// for greater error checking, but we don't really do any anyway. We
// will just track it with next_code and overwrite old entries.
next_code = eoi_code + 1;
cur_code_size = min_code_size + 1;
code_mask = (1 << cur_code_size) - 1;
// Don't update prev_code ?
prev_code = null;
continue;
} else if (code === eoi_code) {
break;
}
// We have a similar situation as the decoder, where we want to store
// variable length entries (code table entries), but we want to do in a
// faster manner than an array of arrays. The code below stores sort of a
// linked list within the code table, and then "chases" through it to
// construct the dictionary entries. When a new entry is created, just the
// last byte is stored, and the rest (prefix) of the entry is only
// referenced by its table entry. Then the code chases through the
// prefixes until it reaches a single byte code. We have to chase twice,
// first to compute the length, and then to actually copy the data to the
// output (backwards, since we know the length). The alternative would be
// storing something in an intermediate stack, but that doesn't make any
// more sense. I implemented an approach where it also stored the length
// in the code table, although it's a bit tricky because you run out of
// bits (12 + 12 + 8), but I didn't measure much improvements (the table
// entries are generally not the long). Even when I created benchmarks for
// very long table entries the complexity did not seem worth it.
// The code table stores the prefix entry in 12 bits and then the suffix
// byte in 8 bits, so each entry is 20 bits.
var chase_code = code < next_code ? code : prev_code;
// Chase what we will output, either {CODE} or {CODE-1}.
var chase_length = 0;
var chase = chase_code;
while (chase > clear_code) {
chase = code_table[chase] >> 8;
++chase_length;
}
var k = chase;
var op_end = op + chase_length + (chase_code !== code ? 1 : 0);
if (op_end > output_length) {
console.log("Warning, gif stream longer than expected.");
return;
}
// Already have the first byte from the chase, might as well write it fast.
output[op++] = k;
op += chase_length;
var b = op; // Track pointer, writing backwards.
if (chase_code !== code) // The case of emitting {CODE-1} + k.
output[op++] = k;
chase = chase_code;
while (chase_length--) {
chase = code_table[chase];
output[--b] = chase & 0xff; // Write backwards.
chase >>= 8; // Pull down to the prefix code.
}
if (prev_code !== null && next_code < 4096) {
code_table[next_code++] = prev_code << 8 | k;
// TODO(deanm): Figure out this clearing vs code growth logic better. I
// have an feeling that it should just happen somewhere else, for now it
// is awkward between when we grow past the max and then hit a clear code.
// For now just check if we hit the max 12-bits (then a clear code should
// follow, also of course encoded in 12-bits).
if (next_code >= code_mask+1 && cur_code_size < 12) {
++cur_code_size;
code_mask = code_mask << 1 | 1;
}
}
prev_code = code;
}
if (op !== output_length) {
console.log("Warning, gif stream shorter than expected.");
}
return output;
}
// CommonJS.
try { exports.GifWriter = GifWriter; exports.GifReader = GifReader } catch(e) {}

View file

@ -8,7 +8,7 @@ var splashScreen = `
`;
var addDmScreen = `
<div style="display: flex;justify-content: center;align-items: center;height:100%;flex-direction: column;text-align: center;">
<svg xmlns="http://www.w3.org/2000/svg" width="3rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M120-160v-600q0-33 23.5-56.5T200-840h480q33 0 56.5 23.5T760-760v203q-10-2-20-2.5t-20-.5q-10 0-20 .5t-20 2.5v-203H200v400h283q-2 10-2.5 20t-.5 20q0 10 .5 20t2.5 20H240L120-160Zm160-440h320v-80H280v80Zm0 160h200v-80H280v80Zm400 280v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-360v-400 400Z"/></svg>
${ICONS.addChat({ width: "3rem" })}
<herotitle><blah>title.add.chat</blah></herotitle>
<p><blah>desc.add.chat</blah></p>
<br>
@ -24,7 +24,7 @@ var addDmScreen = `
var createGroupScreen = `
<div style="display: flex;justify-content: center;align-items: center;height:100%;flex-direction: column;text-align: center;">
<svg xmlns="http://www.w3.org/2000/svg" width="3rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M500-482q29-32 44.5-73t15.5-85q0-44-15.5-85T500-798q60 8 100 53t40 105q0 60-40 105t-100 53Zm220 322v-120q0-36-16-68.5T662-406q51 18 94.5 46.5T800-280v120h-80Zm80-280v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80Zm-593-87q-47-47-47-113t47-113q47-47 113-47t113 47q47 47 47 113t-47 113q-47 47-113 47t-113-47ZM0-160v-112q0-34 17.5-62.5T64-378q62-31 126-46.5T320-440q66 0 130 15.5T576-378q29 15 46.5 43.5T640-272v112H0Zm320-400q33 0 56.5-23.5T400-640q0-33-23.5-56.5T320-720q-33 0-56.5 23.5T240-640q0 33 23.5 56.5T320-560ZM80-240h480v-32q0-11-5.5-20T540-306q-54-27-109-40.5T320-360q-56 0-111 13.5T100-306q-9 5-14.5 14T80-272v32Zm240-400Zm0 400Z"/></svg>
${ICONS.createGroup({ width: "3rem" })}
<herotitle><blah>title.create.group</blah></herotitle>
<p><blah>desc.create.group</blah></p>
<br>
@ -40,7 +40,7 @@ var createGroupScreen = `
var createSpaceScreen = `
<div style="display: flex;justify-content: center;align-items: center;height:100%;flex-direction: column;text-align: center;">
<svg xmlns="http://www.w3.org/2000/svg" width="3rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M80-120v-720h400v160h400v320h-80v-240H480v80h80v80h-80v80h80v80h-80v80h160v80H80Zm80-80h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm160 480h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80ZM800-40v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80ZM640-440v-80h80v80h-80Zm0 160v-80h80v80h-80Z"/></svg>
${ICONS.createSpace({ width: "3rem" })}
<herotitle><blah>title.create.space</blah></herotitle>
<p><blah>desc.create.space</blah></p>
<br>
@ -55,7 +55,7 @@ var createSpaceScreen = `
`;
var joinSpaceScreen = `
<div style="display: flex;justify-content: center;align-items: center;height:100%;flex-direction: column;text-align: center;">
<svg xmlns="http://www.w3.org/2000/svg" width="3rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm200 160v-80h160q50 0 85-35t35-85q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 83-58.5 141.5T680-280H520Z"/></svg>
${ICONS.joinSpace({ width: "3rem" })}
<herotitle><blah>title.join.space</blah></herotitle>
<p><blah>desc.join.space</blah></p>
<br>
@ -73,30 +73,83 @@ var joinSpaceScreen = `
</div>
`;
var invitesEmptyState = `
${ICONS.invites({ width: "4rem" })}
<br>
<p><blah>desc.no.invites</blah></p>
`;
var invitesLoadingState = `
${ICONS.invites({ width: "4rem" })}
<br>
<p><blah>desc.fetching.invites</blah></p>
`;
var invitesScreen = `
<div style="display: flex; flex-direction: column; height: 100%;">
<div style="display: flex; gap: 0.5rem; padding: 0.5rem 1rem; border-bottom: var(--border-width) solid var(--light-border-color); flex-shrink: 0;">
<button id="tab-invites-received" class="submit-button blah tab-inactive" onclick="switchInvitesTab('received')" style="margin:0; padding: 0 1.5rem;">title.received</button>
<button id="tab-invites-sent" class="submit-button blah tab-inactive" onclick="switchInvitesTab('sent')" style="margin:0; padding: 0 1.5rem;">title.sent</button>
</div>
<div id="invites-container" style="flex-grow: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; opacity: 0.5;">
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>
<br>
<p><blah>desc.no.invites</blah></p>
<div id="invites-container" class="list-container empty">
${invitesLoadingState}
</div>
</div>
`;
var notifisEmptyState = `
${ICONS.notifications({ width: "4rem" })}
<br>
<p><blah>desc.no.notifications</blah></p>
`;
var notifisScreen = `
<div style="display: flex; flex-direction: column; height: 100%;">
<div style="display: flex; gap: 0.5rem; padding: 0.5rem 1rem; border-bottom: var(--border-width) solid var(--light-border-color); flex-shrink: 0;">
<button id="tab-notifis-all" class="submit-button blah tab-inactive" onclick="switchNotifisTab('all')" style="margin:0; padding: 0 1.5rem;">title.all</button>
<button id="tab-notifis-unread" class="submit-button blah tab-inactive" onclick="switchNotifisTab('unread')" style="margin:0; padding: 0 1.5rem;">title.unread</button>
</div>
<div id="notifis-container" style="flex-grow: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; opacity: 0.5;">
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-200v-80h80v-280q0-83 50-147.5T420-792v-28q0-25 17.5-42.5T480-880q25 0 42.5 17.5T540-820v28q80 20 130 84.5T720-560v280h80v80H160Zm320-300Zm0 420q-33 0-56.5-23.5T400-160h160q0 33-23.5 56.5T480-80ZM320-280h320v-280q0-66-47-113t-113-47q-66 0-113 47t-47 113v280Z"/></svg>
<br>
<p><blah>desc.no.notifications</blah></p>
<div id="notifis-container" class="list-container empty">
${notifisEmptyState}
</div>
</div>
`;
var invitesEntry = `
<div class="invite-entry">
<div class="invite-info">
<img src="{pfp}" class="invite-pfp" onclick="openProfile('{id}')" style="cursor: pointer;">
<div class="invite-details">
<span class="invite-name" onclick="openProfile('{id}')" style="cursor: pointer;">{username}</span>
<span class="invite-desc"><blah>{desc}</blah></span>
</div>
</div>
<div class="invite-actions">
{actions}
</div>
</div>
`;
var chatScreen = `
<div style="display: flex; flex-direction: column; height: 100%; width: 100%;">
<div id="chat-messages" style="flex-grow: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
</div>
<div id="replying-bar" style="display: none; padding: 0 1rem; border-top: var(--border-width) solid var(--light-border-color); font-size: 0.85rem; justify-content: space-between; align-items: center; background: var(--main-bg-color);">
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-grow: 1;">
<span style="opacity: 0.7;"><blah>title.replying.to</blah> </span><strong id="replying-to-name"></strong>: <span id="replying-to-text" style="opacity: 0.7;"></span>
</div>
<button onclick="cancelReply()" style="background: none; border: none; color: var(--text-color); cursor: pointer; padding: 0.2rem; margin-left: 0.5rem;">
${ICONS.close({ width: "1.2rem", fill: "currentColor" })}
</button>
</div>
<div id="attachments-preview" style="display: none; padding: 0.5rem 1rem; gap: 0.8rem; overflow-x: auto; border-top: var(--border-width) solid var(--light-border-color); background: var(--main-bg-color); align-items: center; flex-shrink: 0;"></div>
<div style="padding: 1rem; border-top: var(--border-width) solid var(--light-border-color); display: flex; gap: 0.5rem; align-items: flex-end; flex-shrink: 0;">
<button id="chat-add-attachment" class="submit-button" onclick="clickAddAttachment(this, event)" style="margin: 0; padding: 0; width: 2.5rem; flex-shrink: 0; min-height: 2.5rem; max-height: 8rem;">
${ICONS.plus({ width: "1.5rem", height: "1.5rem", stroke: "var(--main-bg-color)" })}
</button>
<textarea id="chat-input" class="forminput" style="flex-grow: 1; margin: 0; resize: none; min-height: 2.5rem; max-height: 8rem; padding: 0.5rem;" placeholder="{blah(placeholder.message.input)}" onkeydown="handleChatInputKey(event)" oninput="handleChatInputResize(this)"></textarea>
<button id="chat-sendmessage" class="submit-button" onclick="sendMessage(this)" style="margin: 0; padding: 0; width: 2.5rem; flex-shrink: 0; min-height: 2.5rem; max-height: 8rem;">
${ICONS.send()}
</button>
</div>
</div>
`;
@ -106,49 +159,45 @@ var homeRoomBar = `
<div class="sidebar-section-header">
<button class="collapse-text-button" id="collapse-dms" onclick="clickCollapseDms()">
<span><blah>title.dms</blah></span>
<svg class="chevron" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="var(--text-color)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
${ICONS.chevronDown({ className: "chevron" })}
</button>
<button class="add-action-button" id="add-dm-btn" onclick="clickAddDm()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="var(--text-color)"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
${ICONS.plus()}
</button>
</div>
<div id="dms-wrapper" class="collapsible-wrapper">
<div class="collapsible-inner">
<div id="dms-list" class="list-container no-flex-grow empty" style="padding: 0 0.5rem;"></div>
</div>
</div>
<div class="sidebar-section-header">
<button class="collapse-text-button" id="collapse-groups" onclick="clickCollapseGroups()">
<span><blah>title.groups</blah></span>
<svg class="chevron" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="var(--text-color)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
${ICONS.chevronDown({ className: "chevron" })}
</button>
<button class="add-action-button" id="add-group-btn" onclick="clickAddGroup()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="var(--text-color)"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
${ICONS.plus()}
</button>
</div>
<div id="groups-wrapper" class="collapsible-wrapper">
<div class="collapsible-inner">
<div id="groups-list" class="list-container no-flex-grow empty" style="padding: 0 0.5rem;"></div>
</div>
</div>
`;
var inboxRoomBar = `
<div class="sidebar-section-header">
<button class="collapse-text-button" id="collapse-invites" onclick="gotoInvites()">
<svg xmlns="http://www.w3.org/2000/svg" class="chevron" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>
${ICONS.invites({ className: "chevron", width: "24" })}
<span><blah>title.invites</blah></span>
</button>
</div>
<div class="sidebar-section-header">
<button class="collapse-text-button" id="collapse-notifis" onclick="gotoNotifis()">
<svg xmlns="http://www.w3.org/2000/svg" class="chevron" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-200v-80h80v-280q0-83 50-147.5T420-792v-28q0-25 17.5-42.5T480-880q25 0 42.5 17.5T540-820v28q80 20 130 84.5T720-560v280h80v80H160Zm320-300Zm0 420q-33 0-56.5-23.5T400-160h160q0 33-23.5 56.5T480-80ZM320-280h320v-280q0-66-47-113t-113-47q-66 0-113 47t-47 113v280Z"/></svg>
${ICONS.notifications({ className: "chevron", width: "24" })}
<span><blah>title.notifications</blah></span>
</button>
</div>
@ -156,18 +205,169 @@ var inboxRoomBar = `
//context menus
var profileContextMenu = `
<button onclick="openProfile(window.id)">
${ICONS.profile()}
<span class="blah">title.profile</span>
</button>
<button onclick="gotoSettings()">
${ICONS.settingsProfile()}
<span class="blah">title.settings</span>
</button>
<button onclick="logout()">
${ICONS.delete({ fill: "var(--big-red)" })}
<span class="blah" style="color: var(--big-red)">title.logout</span>
</button>
`;
var attachmentMenu = `
<button onclick="document.getElementById('attach-take-photo').click(); fixedContextMenu.classList.remove('show');">
${ICONS.camera()}
<span class="blah">action.attachment.take.photo</span>
</button>
<button onclick="document.getElementById('attach-media').click(); fixedContextMenu.classList.remove('show');">
${ICONS.image()}
<span class="blah">action.attachment.media</span>
</button>
<button onclick="document.getElementById('attach-file').click(); fixedContextMenu.classList.remove('show');">
${ICONS.fileText()}
<span class="blah">action.attachment.file</span>
</button>
<button onclick="console.log('poll'); fixedContextMenu.classList.remove('show');">
${ICONS.poll()}
<span class="blah">action.attachment.poll</span>
</button>
<input type="file" id="attach-take-photo" accept="image/*" capture="environment" style="display: none;" onchange="handleAttachmentSelection(event)" multiple>
<input type="file" id="attach-media" accept="image/*,video/*" style="display: none;" onchange="handleAttachmentSelection(event)" multiple>
<input type="file" id="attach-file" style="display: none;" onchange="handleAttachmentSelection(event)" multiple>
`;
var addSpaceMenu = `
<button onclick="gotoJoinSpace()">
<svg xmlns="http://www.w3.org/2000/svg" width="1.4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm200 160v-80h160q50 0 85-35t35-85q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 83-58.5 141.5T680-280H520Z"/></svg>
${ICONS.joinSpace()}
<span class="blah">title.join.space</span>
</button>
<button onclick="gotoCreateSpace()">
<svg xmlns="http://www.w3.org/2000/svg" width="1.4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M80-120v-720h400v160h400v320h-80v-240H480v80h80v80h-80v80h80v80h-80v80h160v80H80Zm80-80h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm160 480h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80ZM800-40v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80ZM640-440v-80h80v80h-80Zm0 160v-80h80v80h-80Z"/></svg>
${ICONS.createSpace()}
<span class="blah">title.create.space</span>
</button>`
var messageContextMenu = `
<button onclick="replyMessage(currentContextMenuMsgId)">
${ICONS.replyAction()}
<span class="blah">title.reply.message</span>
</button>
<button id="context-react-btn" onclick="openReactionPicker(event)">
${ICONS.react()}
<span class="blah">title.react.message</span>
</button>
<button id="context-delete-btn" onclick="deleteMessage(currentContextMenuMsgId)">
${ICONS.delete({ fill: "var(--big-red)" })}
<span class="blah" style="color: var(--big-red)">title.delete.message</span>
</button>
`;
//elements
var detailsBtn = `<button class="mobile-nav-btn right" onclick="mobileNavDetails()"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z"/></svg></button>`;
var backBtnHtml = `<button class="mobile-nav-btn" onclick="mobileNavBack()"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="m313-440 224 224-57 56-320-320 320-320 57 56-224 224h487v80H313Z"/></svg></button>`;
var detailsBtn = `<button class="mobile-nav-btn right" onclick="mobileNavDetails()">${ICONS.menu({ width: "24", height: "24" })}</button>`;
var backBtnHtml = `<button class="mobile-nav-btn" onclick="mobileNavBack()">${ICONS.arrowBack({ width: "24", height: "24" })}</button>`;
var pinBtnHtml = `<button class="topbar-action-btn room-action-pin right">${ICONS.pin({ width: "24", height: "24" })}</button>`;
var callBtnHtml = `<button class="topbar-action-btn room-action-call right">${ICONS.call({ width: "24", height: "24" })}</button>`;
var settingsBar = `
<div class="sidebar-section-header">
<button class="collapse-text-button" id="tab-settings-account" onclick="switchSettingsTab('account')">
${ICONS.account({ className: "chevron", width: "24" })}
<span><blah>title.account</blah></span>
</button>
</div>
<div class="sidebar-section-header">
<button class="collapse-text-button" id="tab-settings-profile" onclick="switchSettingsTab('profile')">
${ICONS.account({ className: "chevron", width: "24" })}
<span><blah>title.profile</blah></span>
</button>
</div>
<div class="sidebar-section-header">
<button class="collapse-text-button" id="tab-settings-appearance" onclick="switchSettingsTab('appearance')">
${ICONS.settings({ className: "chevron", width: "24" })}
<span><blah>title.appearance</blah></span>
</button>
</div>
`;
var accountSettings = `
<div style="display: flex;justify-content: center;align-items: center;height:100%;flex-direction: column;text-align: center;">
${ICONS.profile({ width: "3rem" })}
<herotitle><blah>title.account</blah></herotitle>
<br>
<div class="input-group">
<label for="settings-username"><blah>title.username</blah></label>
<input type="text" id="settings-username" class="forminput">
</div>
<div class="input-group">
<button class="submit-button blah" onclick="changeUsername()">action.save</button>
</div>
<div class="input-group">
<label for="settings-password"><blah>title.password</blah></label>
<input type="password" id="settings-password" class="forminput">
</div>
<div class="input-group">
<button class="submit-button blah" onclick="changePassword()">action.save</button>
</div>
<br>
<div class="input-group">
<label style="color: var(--big-red);"><blah>title.delete.account</blah></label>
<p style="font-size: 0.85rem; opacity: 0.7; margin-bottom: 0.5rem; margin-left: 0.6rem;"><blah>desc.delete.account</blah></p>
<button class="submit-button blah" onclick="deleteAccount()" style="background: var(--big-red); color: white;">action.delete.account</button>
</div>
</div>
`;
var profileSettings = `
<div style="display: flex;justify-content: center;align-items: center;height:100%;flex-direction: column;text-align: center;">
${ICONS.account({ width: "3rem" })}
<herotitle><blah>title.profile</blah></herotitle>
<br>
<div class="input-group" style="display: flex; flex-direction: row; align-items: stretch; justify-content: space-between; border: var(--border-width) solid var(--light-border-color); border-radius: 0.8rem; padding: 1rem; width: 100%; max-width: 30rem;">
<img id="settings-avatar-preview" src="" style="width: 4.5rem; height: 4.5rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255, 255, 255, 0.2); object-fit: cover; background: var(--main-bg-color);">
<div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;">
<div style="font-weight: bold;"><blah>title.avatar</blah></div>
<button class="submit-button blah" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('settings-avatar-input').click()">action.change.avatar</button>
<input type="file" id="settings-avatar-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'avatar')">
</div>
</div>
<br>
<div class="input-group" style="display: flex; flex-direction: row; align-items: stretch; justify-content: space-between; border: var(--border-width) solid var(--light-border-color); border-radius: 0.8rem; padding: 1rem; width: 100%; max-width: 30rem;">
<div style="width: 12.4rem; aspect-ratio: 494 / 200; background: var(--main-bg-color); border-radius: 0.4rem; overflow: hidden; display: flex; align-items: center; justify-content: center; border: var(--border-width) solid rgba(255, 255, 255, 0.2);">
<img id="settings-banner-preview" src="" style="width: 100%; height: 100%; object-fit: cover;">
</div>
<div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;">
<div style="font-weight: bold;"><blah>title.banner</blah></div>
<button class="submit-button blah" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('settings-banner-input').click()">action.change.banner</button>
<input type="file" id="settings-banner-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'banner')">
</div>
</div>
<br>
<div class="input-group">
<label for="settings-bio"><blah>title.bio</blah></label>
<textarea id="settings-bio" class="forminput" style="min-height: 8rem; padding-top: 0.8rem;"></textarea>
</div>
<div class="input-group">
<button class="submit-button blah" onclick="saveProfile()">action.save.bio</button>
</div>
</div>
`;
var appearanceSettings = `
<div style="display: flex;justify-content: center;align-items: center;height:100%;flex-direction: column;text-align: center;">
${ICONS.settings({ width: "3rem" })}
<herotitle><blah>title.appearance</blah></herotitle>
<br>
<div class="input-group">
<label for="settings-language"><blah>title.language</blah></label>
<select id="settings-language" onchange="changeLanguage(this.value)" style="border-radius: 0.8rem; background-color: rgba(255, 255, 255, 0.05); margin: var(--button-margin); padding: var(--button-margin); padding-left: calc(var(--button-margin) * 2); padding-right: 1.5rem; height: var(--button-height); display: flex; cursor: pointer; align-items: center; justify-content: flex-start; border: var(--border-width) solid var(--light-border-color); color: var(--text-color); font-family: inherit; font-size: inherit; appearance: none; outline: none;">
<option value="" class="blah">title.loading</option>
</select>
</div>
</div>
`;

View file

@ -2,6 +2,10 @@
font-family: 'Nunito';
src: URL('Nunito-Regular.ttf') format('truetype');
}
@font-face {
font-family: 'NotoColorEmoji';
src: URL('NotoColorEmoji-Regular.ttf') format('truetype');
}
:root {
--main-bg-color: rgb(20, 20, 20);
--text-color: rgb(240, 240, 245);
@ -14,9 +18,9 @@
--button-height: 2.4rem;
--border-width: 0.09rem;
--big-default: rgb(207 207 207);
--big-red: rgb(202 0 0);
--big-green: rgb(25 189 0);
--big-default: rgb(207, 207, 207);
--big-red: hsl(0, 60%, 55%);
--big-green: hsl(120, 60%, 55%);
}
*:focus {
outline: none;
@ -66,12 +70,20 @@ loading {
padding: 0;
transition: all 0.2s ease;
color: var(--text-color);
font-family: "Nunito", sans-serif;
font-family: "Nunito", "NotoColorEmoji", sans-serif;
-webkit-tap-highlight-color: transparent;
user-select: none !important;
-webkit-user-drag: none !important;
scrollbar-width: none;
-ms-overflow-style: none;
}
button, input {
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
background: transparent;
}
button, input, textarea {
border-radius: 0.8rem;
background-color: rgba(255, 255, 255, 0.05);
margin: var(--button-margin);
@ -92,12 +104,23 @@ button, input {
touch-action: manipulation;
-webkit-touch-callout: none;
}
input {
input, textarea {
padding-left: calc(var(--button-margin) * 2);
-webkit-touch-callout: default;
user-select: text !important;
-webkit-user-select: text !important;
}
textarea {
resize: none;
}
.chat-message-text, .chat-message-author, .chat-message-timestamp,
.profile-name, .profile-bio, .profile-id-host,
.dm-username, .group-name, .room-name {
user-select: text !important;
-webkit-user-select: text !important;
cursor: auto;
}
indicator {
@ -148,6 +171,22 @@ indicator.active {
width: calc(var(--icon-button-height) - (var(--border-width) * 2));
height: calc(var(--icon-button-height) - (var(--border-width) * 2));
pointer-events: none !important;
border-radius: 0.65rem;
}
button {
overflow: hidden;
}
button svg {
pointer-events: none !important;
}
button img {
pointer-events: none !important;
}
.reaction-pill {
backface-visibility: hidden;
transform: translateZ(0);
}
roomcontent {
@ -156,6 +195,7 @@ roomcontent {
height: 100%;
flex-grow: 1;
min-width: 0;
border-right: var(--border-width) solid var(--light-border-color);
}
roomcontent2 {
@ -169,6 +209,7 @@ roomcontent2 {
sidebar {
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 100%;
width: calc(var(--icon-button-height) + (var(--button-margin) * 2) + var(--border-width));
@ -178,6 +219,7 @@ sidebar.second {
width: 17rem;
min-width: 17rem;
max-width: 17rem;
flex-shrink: 0;
}
.sidebar-section-header {
display: flex;
@ -229,7 +271,7 @@ hr {
top: 1.5rem;
left: 50%;
transform: translateX(-50%);
z-index: 1500;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 0.8rem;
@ -252,11 +294,11 @@ hr {
box-shadow: 0 0.5rem 2rem rgba(0, 0, 0, 0.4);
opacity: 0;
transform: translateY(-1rem);
transform: translateY(-2rem);
transition:
opacity 0.3s ease,
transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275),
background-color 0.2s ease;
opacity 0.2s ease,
transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275),
background-color 0.15s ease;
}
.notification.show {
opacity: 1;
@ -277,6 +319,8 @@ roomtopbar {
flex-direction: row;
align-items: center;
z-index: 10;
background-color: var(--main-bg-color);
width: 100%;
border-bottom: var(--border-width) solid var(--light-border-color);
@ -330,52 +374,7 @@ herotitle {
.bottom-element {
margin-top: auto;
}
@media (hover: hover) {
sidebarelement:hover indicator:not(.active) {
content: "";
position: absolute;
width: 0;
height: 1.2rem;
left: -0.08rem;
border: 0.15rem solid var(--text-color);
border-radius: 0.8rem;
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
}
button:hover, input:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.submit-button:hover {
background-color: rgba(255, 255, 255, 0.5);
}
.add-action-button:hover {
background-color: rgba(255, 255, 255, 0.1);
color: var(--text-color);
}
}
button:active, button.is-pressed, input:active, input.is-pressed {
background-color: rgba(255, 255, 255, 0.1);
transform: var(--press-scale);
}
button.active, input.active {
background-color: rgba(255, 255, 255, 0.1);
}
.submit-button:active, .submit-button.is-pressed {
background-color: rgba(255, 255, 255, 0.5);
}
.add-action-button:active, .add-action-button.is-pressed {
background-color: rgba(255, 255, 255, 0.1);
color: var(--text-color);
}
sidebarelement:has(.icon-button:active) indicator:not(.active) {
content: "";
position: absolute;
width: 0;
height: 1.2rem;
left: -0.08rem;
border: 0.15rem solid var(--text-color);
border-radius: 0.8rem;
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
}
blah, inherit, .inherit {
all: inherit;
padding: 0;
@ -419,8 +418,33 @@ space{
gap: 0.5rem;
font-weight: 600;
}
.context-menu button:hover {
background-color: rgba(255, 255, 255, 0.08);
.topbar-action-btn {
display: flex;
background: transparent;
border: none;
height: 2.4rem;
width: 2.4rem;
padding: 0;
margin: 0;
margin-right: 0.8rem;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: var(--text-color);
cursor: pointer;
}
.topbar-action-btn:active, .topbar-action-btn.is-pressed {
background-color: rgba(255, 255, 255, 0.1);
transform: var(--press-scale);
}
.topbar-action-btn.right {
margin-right: 0;
margin-left: 0.2rem;
}
.topbar-action-btn svg {
width: 1.5rem;
height: 1.5rem;
}
.mobile-nav-btn {
display: none;
@ -443,7 +467,7 @@ space{
}
.mobile-nav-btn.right {
margin-right: 0;
margin-left: 0.8rem;
margin-left: 0.2rem;
}
.mobile-nav-btn svg {
width: 1.5rem;
@ -452,7 +476,7 @@ space{
.flex-spacer {
flex-grow: 1;
}
@media (max-width: 52rem) {
@media (max-width: 55rem) {
.mobile-nav-btn {
display: flex;
}
@ -466,7 +490,7 @@ space{
sidebar, sidebar.second, roomcontent {
position: absolute !important;
height: 100%;
transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
transition: all 0.25s cubic-bezier(0.25, 1, 0.5, 1);
}
sidebar {
@ -537,7 +561,7 @@ space{
opacity: 0;
pointer-events: none;
transition: opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1);
transition: all 0.25s cubic-bezier(0.25, 1, 0.5, 1);
}
main.mobile-details roomcontent::after {
@ -554,3 +578,453 @@ space{
background-color: transparent;
color: var(--text-color);
}
.list-container {
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 1rem;
overflow-y: auto;
gap: 0.5rem;
}
.list-container.empty {
justify-content: center;
text-align: center;
opacity: 0.5;
}
.list-container.no-flex-grow {
flex-grow: 0;
}
.collapsible-wrapper {
display: grid;
grid-template-rows: 1fr;
transition: grid-template-rows 0.25s cubic-bezier(0.25, 1, 0.5, 1);
}
.collapsible-wrapper.collapsed {
grid-template-rows: 0fr;
}
.collapsible-inner {
overflow: hidden;
}
.invite-entry {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.8rem 1rem;
border: var(--border-width) solid var(--light-border-color);
border-radius: 0.8rem;
width: 100%;
background: rgba(255, 255, 255, 0.02);
flex-shrink: 0;
}
.invite-info {
display: flex;
align-items: center;
gap: 1rem;
}
.invite-pfp {
width: 2.8rem;
height: 2.8rem;
border-radius: 0.8rem;
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
object-fit: cover;
}
.invite-details {
display: flex;
flex-direction: column;
text-align: left;
}
.invite-name {
font-weight: 700;
font-size: 1.05rem;
}
.invite-desc {
font-size: 0.85rem;
opacity: 0.6;
}
.invite-actions {
display: flex;
gap: 0.5rem;
}
.invite-actions button {
margin: 0;
border-color: rgba(255, 255, 255, 0.2);
width: 3.1rem;
height: 3.1rem;
}
.room-entry {
display: flex;
align-items: center;
gap: 0.1rem;
padding: 1.5rem 0.6rem;
width: 100%;
border: none;
background: transparent;
border-radius: 0.8rem;
flex-shrink: 0;
cursor: pointer;
margin: 0;
}
.room-pfp {
width: 2.2rem;
height: 2.2rem;
border-radius: 0.65rem;
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
object-fit: cover;
pointer-events: none;
}
.room-name {
font-weight: 600;
font-size: 1.05rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
pointer-events: none;
}
.chat-message {
display: flex;
gap: 1rem;
padding: 0.4rem 0.6rem;
margin: 0 -0.6rem;
border-radius: 0.8rem;
transition: background-color 0.15s ease;
}
@media (hover: hover) {
.chat-message:hover {
background-color: rgba(255, 255, 255, 0.04);
}
}
.chat-message:active, .chat-message.context-menu-open {
background-color: rgba(255, 255, 255, 0.04);
}
.chat-message.with-avatar {
margin-top: 0.8rem;
}
.chat-message-pfp {
width: 2.5rem;
height: 2.5rem;
border-radius: 0.65rem;
object-fit: cover;
flex-shrink: 0;
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
}
.chat-message-content {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.chat-message-header {
display: flex;
align-items: baseline;
gap: 0.5rem;
margin-bottom: 0.2rem;
display: none; /* hidden for consecutive */
}
.chat-message.with-avatar .chat-message-header {
display: flex;
}
.chat-message-author {
font-weight: bold;
font-size: 1.05rem;
}
.chat-message-timestamp {
font-size: 0.75rem;
opacity: 0.5;
}
.chat-message-text {
line-height: 1.4;
word-break: break-word;
white-space: pre-wrap;
}
@media (hover: hover) {
sidebarelement:hover indicator:not(.active) {
content: "";
position: absolute;
width: 0;
height: 1.2rem;
left: -0.08rem;
border: 0.15rem solid var(--text-color);
border-radius: 0.8rem;
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
}
button:hover, input:hover, textarea:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.submit-button:hover, .profile-action-btn:hover {
background-color: rgba(255, 255, 255, 0.5);
}
.add-action-button:hover {
background-color: rgba(255, 255, 255, 0.1);
color: var(--text-color);
}
.context-menu button:hover {
background-color: rgba(255, 255, 255, 0.08);
}
.invite-actions button:hover {
filter: brightness(0.93);
}
}
button:active, button.is-pressed, input:active, input.is-pressed, textarea:active, textarea.is-pressed {
background-color: rgba(255, 255, 255, 0.1);
transform: var(--press-scale);
}
.invite-actions button:active {
filter: brightness(0.85);
}
button.active, input.active, textarea.active {
background-color: rgba(255, 255, 255, 0.1);
}
.submit-button:active, .submit-button.is-pressed, .profile-action-btn:active, .profile-action-btn.is-pressed {
background-color: rgba(255, 255, 255, 0.5);
}
.add-action-button:active, .add-action-button.is-pressed {
background-color: rgba(255, 255, 255, 0.1);
color: var(--text-color);
}
.context-menu button:active, .context-menu button.is-pressed {
background-color: rgba(255, 255, 255, 0.08);
}
sidebarelement:has(.icon-button:active) indicator:not(.active) {
content: "";
position: absolute;
width: 0;
height: 1.2rem;
left: -0.08rem;
border: 0.15rem solid var(--text-color);
border-radius: 0.8rem;
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
}
.context-menu button.reaction-text-btn {
background-color: rgba(255, 255, 255, 0.03);
background: rgba(255, 255, 255, 0.05);
}
.context-menu button.reaction-text-btn:hover {
background-color: rgba(255, 255, 255, 0.06);
background: rgba(255, 255, 255, 0.08);
}
.profile-popup-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 3000;
display: flex;
justify-content: center;
align-items: center;
pointer-events: none;
}
.profile-popup-container.show {
pointer-events: auto;
}
.profile-popup-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0);
backdrop-filter: blur(0);
-webkit-backdrop-filter: blur(0);
transition: background 0.3s ease, backdrop-filter 0.3s ease, -webkit-backdrop-filter 0.3s ease;
}
.profile-popup-container.show .profile-popup-bg {
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(0.25rem);
-webkit-backdrop-filter: blur(0.25rem);
}
.profile-popup-content {
position: relative;
width: 90vw;
max-width: 31rem;
height: 85vh;
max-height: 44rem;
background: var(--main-bg-color);
border: var(--border-width) solid var(--light-border-color);
border-radius: 0.9rem;
overflow: hidden;
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.5);
opacity: 0;
transform: scale(0.6);
transition: all 0.2s ease;
display: flex;
flex-direction: column;
}
.profile-popup-container.show .profile-popup-content {
transform: scale(1);
opacity: 1;
transition: all 0.2s ease;
}
.profile-popup-close-btn {
position: absolute;
top: 1rem;
right: 1rem;
z-index: 10;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(0.25rem);
-webkit-backdrop-filter: blur(0.25rem);
border: none;
width: 2.5rem;
height: 2.5rem;
border-radius: 0.8rem;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
margin: 0;
cursor: pointer;
}
.profile-popup-close-btn svg {
width: 1.5rem;
height: 1.5rem;
}
.profile-popup-close-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
#profile-popup-inner {
width: 100%;
height: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.profile-banner {
width: 100%;
aspect-ratio: 494 / 200;
background: linear-gradient(135deg, rgba(255,255,255,0.05), rgba(255,255,255,0.1));
object-fit: cover;
flex-shrink: 0;
}
.profile-avatar-container {
position: relative;
margin-top: calc(-3.75 / 31 * 100%);
margin-left: calc(2 / 31 * 100%);
width: calc(7.5 / 31 * 100%);
flex-shrink: 0;
}
.profile-avatar-container::before {
content: "";
display: block;
padding-top: 100%;
}
.profile-avatar {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 0.8rem;
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
object-fit: cover;
background: var(--main-bg-color);
}
.profile-info {
padding: 1rem 2rem 2rem 2rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.profile-name {
font-size: 2rem;
font-weight: 800;
line-height: 1.1;
margin-bottom: 0.2rem;
word-break: break-word;
}
.profile-id-host {
font-size: 1rem;
opacity: 0.6;
font-weight: 600;
}
.profile-bio {
font-size: 1.05rem;
line-height: 1.5;
white-space: pre-wrap;
background: rgba(255, 255, 255, 0.03);
padding: 1rem;
border-radius: 0.8rem;
border: var(--border-width) solid var(--light-border-color);
word-break: break-word;
}
.profile-action-btn {
align-self: flex-start;
padding: 0.6rem 1.5rem;
font-weight: 700;
background: var(--text-color);
color: var(--main-bg-color);
border-radius: 0.8rem;
margin-top: 0.5rem;
margin-left: 0;
height: auto;
}
/* Custom Zoom Slider Styling */
#cropper-zoom {
-webkit-appearance: none;
width: 100%;
height: 6px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
outline: none;
margin: 10px 0;
}
#cropper-zoom::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--text-color);
cursor: pointer;
transition: transform 0.2s, background 0.2s;
}
#cropper-zoom::-webkit-slider-thumb:hover {
transform: scale(1.2);
background: #fff;
}
#cropper-zoom::-moz-range-thumb {
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--text-color);
cursor: pointer;
transition: transform 0.2s, background 0.2s;
border: none;
}
#cropper-zoom::-moz-range-thumb:hover {
transform: scale(1.2);
background: #fff;
}
roomcontent roomtopbar .room-action-pin,
roomcontent roomtopbar .room-action-call {
display: flex;
}
#roomdetailsbar roomtopbar .room-action-pin,
#roomdetailsbar roomtopbar .room-action-call {
display: none;
}
@media (max-width: 65rem) and (min-width: 55.001rem) {
roomcontent roomtopbar .room-action-pin { display: none !important; }
#roomdetailsbar roomtopbar .room-action-pin { display: flex !important; }
}
@media (max-width: 58rem) and (min-width: 55.001rem) {
roomcontent roomtopbar .room-action-call { display: none !important; }
#roomdetailsbar roomtopbar .room-action-call { display: flex !important; }
}

View file

@ -1,4 +1,7 @@
//write your custom deployment scripts here
loginHost.value = "olcxja.ovh";
loginHostChanged();
window.addEventListener("load", () => {
loginHost.value = "olcxja.ovh";
loginHostChanged();
});

View file

@ -0,0 +1,75 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
// @ts-ignore
import Module from './webp-wasm.js';
// default webp config
const defaultWebpConfig = {
lossless: 0,
quality: 100,
};
export const encoderVersion = () => __awaiter(void 0, void 0, void 0, function* () {
const module = yield Module();
return module.encoder_version();
});
export const encodeRGB = (rgb, width, height, quality) => __awaiter(void 0, void 0, void 0, function* () {
const module = yield Module();
quality = typeof quality !== 'number' ? 100 : Math.min(100, Math.max(0, quality));
return module.encodeRGB(rgb, width, height, quality);
});
export const encodeRGBA = (rgba, width, height, quality) => __awaiter(void 0, void 0, void 0, function* () {
const module = yield Module();
quality = typeof quality !== 'number' ? 100 : Math.min(100, Math.max(0, quality));
return module.encodeRGBA(rgba, width, height, quality);
});
export const encode = (data, width, height, hasAlpha, config) => __awaiter(void 0, void 0, void 0, function* () {
const module = yield Module();
const webpConfig = Object.assign(Object.assign({}, defaultWebpConfig), config);
webpConfig.lossless = Math.min(1, Math.max(0, webpConfig.lossless));
webpConfig.quality = Math.min(100, Math.max(0, webpConfig.quality));
return module.encode(data, width, height, hasAlpha, webpConfig);
});
export const encodeAnimation = (width, height, hasAlpha, frames) => __awaiter(void 0, void 0, void 0, function* () {
const module = yield Module();
const frameVector = new module.VectorWebPAnimationFrame();
frames.forEach((frame) => {
const hasConfig = frame.config !== undefined;
const config = Object.assign(Object.assign({}, defaultWebpConfig), frame.config);
config.lossless = Math.min(1, Math.max(0, config.lossless));
config.quality = Math.min(100, Math.max(0, config.quality));
frameVector.push_back({
duration: frame.duration,
data: frame.data,
config,
has_config: hasConfig,
});
});
return module.encodeAnimation(width, height, hasAlpha, frameVector);
});
export const decoderVersion = () => __awaiter(void 0, void 0, void 0, function* () {
const module = yield Module();
return module.decoder_version();
});
export const decodeRGB = (data) => __awaiter(void 0, void 0, void 0, function* () {
const module = yield Module();
return module.decodeRGB(data);
});
export const decodeRGBA = (data) => __awaiter(void 0, void 0, void 0, function* () {
const module = yield Module();
return module.decodeRGBA(data);
});
// TODO:
// export const decode = async (data: Uint8Array, hasAlpha: boolean) => {
// const module = await Module()
// return module.decode(data, hasAlpha)
// }
export const decodeAnimation = (data, hasAlpha) => __awaiter(void 0, void 0, void 0, function* () {
const module = yield Module();
return module.decodeAnimation(data, hasAlpha);
});

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -0,0 +1,18 @@
import { encodeAnimation, decodeAnimation } from './wasm-webp/index.js';
self.onmessage = async (e) => {
let action = e.data.action || 'encode';
try {
if (action === 'decode') {
let decoded = await decodeAnimation(e.data.data, true);
let transferables = decoded.map(f => f.data.buffer);
self.postMessage({ success: true, data: decoded, action }, transferables);
} else {
let { targetWidth, targetHeight, webpFrames } = e.data;
let webpUint8 = await encodeAnimation(targetWidth, targetHeight, true, webpFrames);
self.postMessage({ success: true, data: webpUint8, action }, [webpUint8.buffer]);
}
} catch (err) {
self.postMessage({ success: false, error: err.toString(), action });
}
};

View file

@ -1,8 +0,0 @@
[Desktop Entry]
Name=Miarven
Exec=bash -c 'DIR="$(dirname "%k")"; mkdir -p ~/.local/share/icons; cp "$DIR/icon.png" ~/.local/share/icons/olcxjamiarven.png; cd "$DIR" && ./miarven'
Icon=olcxjamiarven
Type=Application
Categories=Internet
StartupNotify=true
StartupWMClass=olcxja.miarven

40
assets/install.sh Normal file
View file

@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -e
echo "WARNING: Make sure you don't run this script in a folder that contains anything more than Miarven"
echo "otherwise it may result in data loss"
read -r -p "Press Enter to continue (or Ctrl+C, to cancel)..."
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BIN_DIR="$HOME/.local/bin/olcxja.miarven"
APP_DIR="$HOME/.local/share/applications"
echo "1. Copying files..."
mkdir -p "$BIN_DIR"
mkdir -p "$APP_DIR"
cp -r "$SCRIPT_DIR/"* "$BIN_DIR/"
echo "2. Creating shortcut..."
mv "$BIN_DIR/olcxja.miarven.desktop" "$APP_DIR/olcxja.miarven.desktop"
echo "3. Adding executable permissions..."
chmod +x "$APP_DIR/olcxja.miarven.desktop"
chmod +x "$BIN_DIR/miarven"
echo "4. Cleaning installation..."
rm -f "$BIN_DIR/olcxja.miarven.desktop"
rm -f "$BIN_DIR/install.sh"
echo "5. Installation completed. Cleaning up and launching..."
rm -rf "$SCRIPT_DIR"/*
"$BIN_DIR/miarven" &
exit 0

View file

@ -0,0 +1,8 @@
[Desktop Entry]
Name=Miarven
Exec=~/.local/bin/olcxja.miarven/miarven
Icon=olcxja.miarven
Type=Application
Categories=Internet
StartupNotify=true
StartupWMClass=olcxja.miarven

View file

@ -1,2 +1,3 @@
cd android
./gradlew assembleRelease
mv app/build/outputs/apk/release/app-release.apk ../build/miarven-android.apk

View file

@ -2,4 +2,6 @@ mkdir build
cd build
npx electron-packager ../electron miarven --platform=linux --arch=x64 --icon=../electron/assets/icon.png --overwrite --asar
cp ../icons/icon.png ./miarven-linux-x64/icon.png
cp ../assets/Miarven.desktop ./miarven-linux-x64/Miarven.desktop
cp ../assets/olcxja.miarven.desktop ./miarven-linux-x64/olcxja.miarven.desktop
cp ../assets/install.sh ./miarven-linux-x64/install.sh
chmod +x ./miarven-linux-x64/install.sh

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

2
electron/node_modules/.bin/asar generated vendored
View file

@ -1 +1 @@
../asar/bin/asar.js
../@electron/asar/bin/asar.js

View file

@ -1 +0,0 @@
../dir-compare/src/cli/dircompare.js

1
electron/node_modules/.bin/electron-fuses generated vendored Symbolic link
View file

@ -0,0 +1 @@
../@electron/fuses/dist/bin.js

View file

@ -1 +1 @@
../electron-osx-sign/bin/electron-osx-flat.js
../@electron/osx-sign/bin/electron-osx-flat.js

View file

@ -1 +1 @@
../electron-osx-sign/bin/electron-osx-sign.js
../@electron/osx-sign/bin/electron-osx-sign.js

View file

@ -1 +1 @@
../electron-rebuild/lib/src/cli.js
../@electron/rebuild/lib/cli.js

1
electron/node_modules/.bin/electron-windows-sign generated vendored Symbolic link
View file

@ -0,0 +1 @@
../@electron/windows-sign/bin/electron-windows-sign.js

View file

@ -1 +0,0 @@
../extract-zip/cli.js

1
electron/node_modules/.bin/install-electron generated vendored Symbolic link
View file

@ -0,0 +1 @@
../electron/install.js

1
electron/node_modules/.bin/is-ci generated vendored
View file

@ -1 +0,0 @@
../is-ci/bin.js

1
electron/node_modules/.bin/jiti generated vendored Symbolic link
View file

@ -0,0 +1 @@
../jiti/lib/jiti-cli.mjs

1
electron/node_modules/.bin/postject generated vendored Symbolic link
View file

@ -0,0 +1 @@
../postject/dist/cli.js

1
electron/node_modules/.bin/read-binary-file-arch generated vendored Symbolic link
View file

@ -0,0 +1 @@
../read-binary-file-arch/cli.js

File diff suppressed because it is too large Load diff

View file

@ -1,9 +0,0 @@
#!/usr/bin/env bash
sz_program=${SZA_PATH:-7za}
sz_type=${SZA_ARCHIVE_TYPE:-xz}
case $1 in
-d) "$sz_program" e -si -so -t${sz_type} ;;
*) "$sz_program" a f -si -so -t${sz_type} -mx${SZA_COMPRESSION_LEVEL:-9} ;;
esac 2> /dev/null

View file

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 Vladimir Krivosheev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1 +0,0 @@
7-Zip precompiled binaries.

View file

@ -1,2 +0,0 @@
export const path7za: string
export const path7x: string

View file

@ -1,22 +0,0 @@
"use strict"
const path = require("path")
function getPath() {
if (process.env.USE_SYSTEM_7ZA === "true") {
return "7za"
}
if (process.platform === "darwin") {
return path.join(__dirname, "mac", process.arch, "7za")
}
else if (process.platform === "win32") {
return path.join(__dirname, "win", process.arch, "7za.exe")
}
else {
return path.join(__dirname, "linux", process.arch, "7za")
}
}
exports.path7za = getPath()
exports.path7x = path.join(__dirname, "7x.sh")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,9 +0,0 @@
#!/usr/bin/env bash
set -e
BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
rm -rf /tmp/7z-linux
mkdir /tmp/7z-linux
cp "$BASEDIR/do-build.sh" /tmp/7z-linux/do-build.sh
docker run --rm -v /tmp/7z-linux:/project buildpack-deps:xenial /project/do-build.sh

View file

@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -e
apt-get update -qq
apt-get upgrade -qq
echo "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main" > /etc/apt/sources.list.d/llvm.list
curl -L http://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
apt-get update -qq
apt-get install -qq bzip2 yasm clang-5.0 lldb-5.0 lld-5.0
ln -s /usr/bin/clang-5.0 /usr/bin/clang
ln -s /usr/bin/clang++-5.0 /usr/bin/clang++
mkdir -p /tmp/7z
cd /tmp/7z
curl -L http://downloads.sourceforge.net/project/p7zip/p7zip/16.02/p7zip_16.02_src_all.tar.bz2 | tar -xj -C . --strip-components 1
cp makefile.linux_clang_amd64_asm makefile.machine
make -j4
mv bin/7za /project/7za

Binary file not shown.

Binary file not shown.

View file

@ -1,20 +0,0 @@
{
"name": "7zip-bin",
"description": "7-Zip precompiled binaries",
"version": "5.1.1",
"files": [
"*.js",
"7x.sh",
"index.d.ts",
"linux",
"mac",
"win"
],
"license": "MIT",
"repository": "develar/7zip-bin",
"keywords": [
"7zip",
"7z",
"7za"
]
}

Binary file not shown.

Binary file not shown.

View file

@ -511,6 +511,15 @@ export interface CapacitorConfig {
packageTraits?: {
[pluginId: string]: string[];
};
/**
* Define options to apply to the package.
* The key is the plugin ID (e.g. `@capacitor-community/device`)
*
* @since 8.4.0
*/
packageOptions?: {
[pluginId: string]: PackageOptions;
};
};
};
};
@ -546,7 +555,7 @@ export interface CapacitorConfig {
* Configure the local scheme on Android.
*
* Custom schemes on Android are unable to change the URL path as of Webview 117. Changing this value from anything other than `http` or `https` can result in your
* application unable to resolve routing. If you must change this for some reason, consider using a hash-based url strategy, but there are no guarentees that this
* application unable to resolve routing. If you must change this for some reason, consider using a hash-based url strategy, but there are no guarantees that this
* will continue to work long term as allowing non-standard schemes to modify query parameters and url fragments is only allowed for compatibility reasons.
* https://ionic.io/blog/capacitor-android-customscheme-issue-with-chrome-117
*
@ -732,3 +741,17 @@ export interface PluginsConfig {
animation?: 'FADE' | 'NONE';
};
}
export interface PackageOptions {
/**
* Create a symlink to the plugin folder instead of pointing to the plugin path.
* Useful when plugin names conflict.
*/
symlink?: boolean;
/**
* Useful to avoid target name conflicts in dependencies
* [see](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/modulealiasing/)
*/
moduleAliases?: {
[target: string]: string;
};
}

View file

@ -4,6 +4,7 @@ exports.installCocoaPodsPlugins = exports.updateIOS = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
const semver_1 = require("semver");
const colors_1 = tslib_1.__importDefault(require("../colors"));
const common_1 = require("../common");
const cordova_1 = require("../cordova");
@ -11,6 +12,7 @@ const errors_1 = require("../errors");
const log_1 = require("../log");
const plugin_1 = require("../plugin");
const copy_1 = require("../tasks/copy");
const migrate_1 = require("../tasks/migrate");
const fs_1 = require("../util/fs");
const iosplugin_1 = require("../util/iosplugin");
const node_1 = require("../util/node");
@ -43,6 +45,22 @@ async function updatePluginFiles(config, plugins, deployment) {
if ((await config.ios.packageManager) === 'SPM') {
await generateCordovaPackageFiles(cordovaPlugins, config);
const validSPMPackages = await (0, spm_1.checkPluginsForPackageSwift)(config, plugins);
await Promise.all(validSPMPackages.map(async (plugin) => {
var _a;
const iosPlatformVersion = await (0, common_1.getCapacitorPackageVersion)(config, config.ios.name);
const packageSwiftPath = (0, path_1.join)(plugin.rootPath, 'Package.swift');
let content = await (0, fs_extra_1.readFile)(packageSwiftPath, { encoding: 'utf-8' });
const regex = new RegExp('url:\\s*"https://github.com/ionic-team/capacitor-swift-pm\\.git",\\s*from:\\s*"([^"]+)"');
const version = (_a = content.match(regex)) === null || _a === void 0 ? void 0 : _a[1];
const majorCapVersion = (0, semver_1.major)(iosPlatformVersion);
if (version && (0, semver_1.major)(version) != majorCapVersion) {
const preCapVersion = (0, semver_1.prerelease)(iosPlatformVersion);
const forceVersion = preCapVersion ? iosPlatformVersion : `${majorCapVersion}.0.0`;
content = (0, migrate_1.setAllStringIn)(content, `url: "https://github.com/ionic-team/capacitor-swift-pm.git",`, `)`, ` from: "${forceVersion}"`);
await (0, fs_extra_1.writeFile)(packageSwiftPath, content);
log_1.logger.warn(`${plugin.id} is built for Capacitor ${(0, semver_1.major)(version)}, it might cause issues`);
}
}));
await (0, spm_1.generatePackageFile)(config, validSPMPackages.concat(cordovaPlugins));
}
else {
@ -59,14 +77,159 @@ async function generateCordovaPackageFiles(cordovaPlugins, config) {
});
}
async function generateCordovaPackageFile(p, config) {
var _a;
const iosPlatformVersion = await (0, common_1.getCapacitorPackageVersion)(config, config.ios.name);
const platformTag = (0, plugin_1.getPluginPlatform)(p, platform);
if ((_a = platformTag.$) === null || _a === void 0 ? void 0 : _a.package) {
const packageSwiftPath = (0, path_1.join)(p.rootPath, 'Package.swift');
let content = await (0, fs_extra_1.readFile)(packageSwiftPath, { encoding: 'utf-8' });
content = content.replace(`apache`, `ionic-team`).replaceAll(`cordova-ios`, `capacitor-swift-pm`);
content = (0, migrate_1.setAllStringIn)(content, `url: "https://github.com/ionic-team/capacitor-swift-pm.git",`, `)`, ` from: "${iosPlatformVersion}"`);
await (0, fs_extra_1.writeFile)(packageSwiftPath, content);
}
else {
await writeGeneratedPackageSwift(p, config, iosPlatformVersion);
}
}
function buildBinaryTargetEntries(p, frameworks) {
const customXcframeworks = frameworks.filter((f) => f.$.custom === 'true' && f.$.src.endsWith('.xcframework'));
const customFrameworks = frameworks.filter((f) => f.$.custom === 'true' && f.$.src.endsWith('.framework'));
if (customFrameworks.length > 0) {
customFrameworks.forEach((f) => {
log_1.logger.warn(`${p.id}: custom .framework files are not supported as binaryTarget in SPM (${f.$.src}). Convert to .xcframework for SPM compatibility.`);
});
}
const binaryTargetsText = customXcframeworks
.map((f) => {
const name = f.$.src.split('/').pop().replace('.xcframework', '');
return `,
.binaryTarget(
name: "${name}",
path: "${f.$.src}"
)`;
})
.join('');
const binaryDepsText = customXcframeworks
.map((f) => {
const name = f.$.src.split('/').pop().replace('.xcframework', '');
return `,\n .target(name: "${name}")`;
})
.join('');
return { binaryTargetsText, binaryDepsText };
}
function buildResourcesText(resources) {
const resourceEntry = [];
for (const resource of resources) {
resourceEntry.push(`.copy("resources/${resource.$.src.split('/').pop()}")`);
}
return resources.length > 0
? `,
resources: [
${resourceEntry.join(',\n ')}
]`
: '';
}
async function buildDependencyTexts(p, config) {
let allDependencies = (0, plugin_1.getPlatformElement)(p, platform, 'dependency');
if (p.xml['dependency']) {
allDependencies = allDependencies.concat(p.xml['dependency']);
}
let packageText = '';
let productText = '';
await Promise.all(allDependencies.map(async (dep) => {
let plugin = dep.$.id;
if (plugin.includes('@') && plugin.indexOf('@') !== 0) {
[plugin] = plugin.split('@');
}
const depPlugin = await (0, plugin_1.resolvePlugin)(config, plugin);
if (depPlugin) {
const headerFiles = (0, plugin_1.getPlatformElement)(depPlugin, platform, 'header-file');
const resources = (0, plugin_1.getPlatformElement)(depPlugin, platform, 'resource-file');
const sourceFiles = (0, plugin_1.getPlatformElement)(depPlugin, platform, 'source-file');
if (sourceFiles.length === 0 && headerFiles.length === 0 && resources.length === 0) {
return;
}
packageText += `,\n .package(name: "${depPlugin.name}", path: "../${depPlugin.name}")`;
productText += `,\n .product(name: "${depPlugin.name}", package: "${depPlugin.name}")`;
}
}));
return { packageText, productText };
}
function buildCSettingsText(p, sourceFiles) {
var _a;
const pluginId = p.id;
const allFlags = new Set();
for (const sourceFile of sourceFiles) {
const flags = (_a = sourceFile.$) === null || _a === void 0 ? void 0 : _a['compiler-flags'];
if (flags) {
flags
.split(/\s+/)
.map((f) => f.trim())
.filter((f) => f.length > 0)
.forEach((f) => allFlags.add(f));
}
}
if (allFlags.size === 0) {
return '';
}
const entries = [];
const unsupportedFlags = [];
for (const flag of allFlags) {
if (flag.startsWith('-D')) {
const definition = flag.slice(2);
const eqIndex = definition.indexOf('=');
if (eqIndex !== -1) {
const name = definition.slice(0, eqIndex);
const value = definition.slice(eqIndex + 1);
entries.push(` .define("${name}", to: "${value}")`);
}
else {
entries.push(` .define("${definition}")`);
}
}
else {
unsupportedFlags.push(flag);
}
}
if (unsupportedFlags.length > 0) {
log_1.logger.warn(`${pluginId}: the following compiler flags are not supported in SPM and were ignored: ${unsupportedFlags.join(', ')}. ` +
`This might cause the plugin to fail to compile.`);
}
if (entries.length === 0) {
return '';
}
return `,
cSettings: [
${entries.join(',\n')}
]`;
}
async function writeGeneratedPackageSwift(p, config, iosPlatformVersion) {
const iosVersion = (0, common_2.getMajoriOSVersion)(config);
const headerFiles = (0, plugin_1.getPlatformElement)(p, platform, 'header-file');
let headersText = '';
if (headerFiles.length > 0) {
headersText = `,
publicHeadersPath: "."`;
const headersText = headerFiles.length > 0
? `,
publicHeadersPath: "."`
: '';
const resources = (0, plugin_1.getPlatformElement)(p, platform, 'resource-file');
const sourceFiles = (0, plugin_1.getPlatformElement)(p, platform, 'source-file');
if (sourceFiles.length === 0 && headerFiles.length === 0 && resources.length === 0) {
return;
}
const frameworks = (0, plugin_1.getPlatformElement)(p, platform, 'framework');
const { binaryTargetsText, binaryDepsText } = buildBinaryTargetEntries(p, frameworks);
const cSettingsText = buildCSettingsText(p, sourceFiles);
const systemFrameworks = frameworks.filter((f) => !f.$.custom && f.$.src.endsWith('.framework'));
const hasWeakFrameworks = systemFrameworks.some((f) => f.$.weak === 'true');
const requiredSystemFrameworks = systemFrameworks.filter((f) => f.$.weak !== 'true');
const resourcesText = buildResourcesText(resources);
const libraryTypeText = hasWeakFrameworks ? `\n type: .dynamic,` : '';
const linkerSettingsText = requiredSystemFrameworks.length > 0
? `,
linkerSettings: [
${requiredSystemFrameworks.map((f) => ` .linkedFramework("${f.$.src.replace('.framework', '')}")`).join(',\n')}
]`
: '';
const { packageText, productText } = await buildDependencyTexts(p, config);
const content = `// swift-tools-version: 5.9
import PackageDescription
@ -76,21 +239,21 @@ let package = Package(
platforms: [.iOS(.v${iosVersion})],
products: [
.library(
name: "${p.name}",
name: "${p.name}",${libraryTypeText}
targets: ["${p.name}"]
)
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "${iosPlatformVersion}")
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "${iosPlatformVersion}")${packageText}
],
targets: [
.target(
name: "${p.name}",
dependencies: [
.product(name: "Cordova", package: "capacitor-swift-pm")
.product(name: "Cordova", package: "capacitor-swift-pm")${binaryDepsText}${productText}
],
path: "."${headersText}
)
path: "."${resourcesText}${headersText}${cSettingsText}${linkerSettingsText}
)${binaryTargetsText}
]
)`;
await (0, fs_extra_1.writeFile)((0, path_1.join)(config.ios.cordovaPluginsDirAbs, 'sources', p.name, 'Package.swift'), content);
@ -354,13 +517,19 @@ function getLinkerFlags(config) {
return '';
}
async function copyPluginsNativeFiles(config, cordovaPlugins) {
var _a;
const isSPM = (await config.ios.packageManager) === 'SPM';
for (const p of cordovaPlugins) {
const platformTag = (0, plugin_1.getPluginPlatform)(p, platform);
if (isSPM && ((_a = platformTag.$) === null || _a === void 0 ? void 0 : _a.package)) {
continue;
}
const sourceFiles = (0, plugin_1.getPlatformElement)(p, platform, 'source-file');
const headerFiles = (0, plugin_1.getPlatformElement)(p, platform, 'header-file');
const codeFiles = sourceFiles.concat(headerFiles);
const frameworks = (0, plugin_1.getPlatformElement)(p, platform, 'framework');
let sourcesFolderName = 'sources';
if ((0, cordova_1.needsStaticPod)(p)) {
if (!isSPM && (0, cordova_1.needsStaticPod)(p)) {
sourcesFolderName += 'static';
}
const sourcesFolder = (0, path_1.join)(config.ios.cordovaPluginsDirAbs, sourcesFolderName, p.name);
@ -371,7 +540,7 @@ async function copyPluginsNativeFiles(config, cordovaPlugins) {
fileName = 'lib' + fileName;
}
let destFolder = sourcesFolderName;
if (codeFile.$['compiler-flags'] && codeFile.$['compiler-flags'] === '-fno-objc-arc') {
if (!isSPM && codeFile.$['compiler-flags'] && codeFile.$['compiler-flags'] === '-fno-objc-arc') {
destFolder = 'noarc';
}
const filePath = (0, plugin_1.getFilePath)(config, p, codeFile.$.src);
@ -390,8 +559,13 @@ async function copyPluginsNativeFiles(config, cordovaPlugins) {
}
if (fileContent.includes('[NSBundle bundleForClass:[self class]]') ||
fileContent.includes('[NSBundle bundleForClass:[CDVCapture class]]')) {
fileContent = fileContent.replace('[NSBundle bundleForClass:[self class]]', '[NSBundle mainBundle]');
fileContent = fileContent.replace('[NSBundle bundleForClass:[CDVCapture class]]', '[NSBundle mainBundle]');
const bundleName = isSPM ? 'SWIFTPM_MODULE_BUNDLE' : '[NSBundle mainBundle]';
fileContent = fileContent.replace('[NSBundle bundleForClass:[self class]]', bundleName);
fileContent = fileContent.replace('[NSBundle bundleForClass:[CDVCapture class]]', bundleName);
await (0, fs_extra_1.writeFile)(fileDest, fileContent, { encoding: 'utf-8' });
}
if (isSPM && fileContent.includes('[NSBundle mainBundle] URLForResource')) {
fileContent = fileContent.replace('[NSBundle mainBundle] URLForResource', 'SWIFTPM_MODULE_BUNDLE URLForResource');
await (0, fs_extra_1.writeFile)(fileDest, fileContent, { encoding: 'utf-8' });
}
if (fileContent.includes('[self.webView superview]') || fileContent.includes('self.webView.superview')) {
@ -405,7 +579,8 @@ async function copyPluginsNativeFiles(config, cordovaPlugins) {
const resourceFiles = (0, plugin_1.getPlatformElement)(p, platform, 'resource-file');
for (const resourceFile of resourceFiles) {
const fileName = resourceFile.$.src.split('/').pop();
await (0, fs_extra_1.copy)((0, plugin_1.getFilePath)(config, p, resourceFile.$.src), (0, path_1.join)(config.ios.cordovaPluginsDirAbs, 'resources', fileName));
const rootResFolder = isSPM ? sourcesFolder : config.ios.cordovaPluginsDirAbs;
await (0, fs_extra_1.copy)((0, plugin_1.getFilePath)(config, p, resourceFile.$.src), (0, path_1.join)(rootResFolder, 'resources', fileName));
}
for (const framework of frameworks) {
if (framework.$.custom && framework.$.custom === 'true') {
@ -416,6 +591,9 @@ async function copyPluginsNativeFiles(config, cordovaPlugins) {
}
async function removePluginsNativeFiles(config) {
await (0, fs_extra_1.remove)(config.ios.cordovaPluginsDirAbs);
if ((await config.ios.packageManager) === 'SPM') {
await (0, fs_extra_1.remove)((0, path_1.join)(config.ios.nativeProjectDirAbs, 'CapApp-SPM', 'symlinks'));
}
await (0, template_1.extractTemplate)(config.cli.assets.ios.cordovaPluginsTemplateArchiveAbs, config.ios.cordovaPluginsDirAbs);
}
function filterResources(plugin) {

View file

@ -36,16 +36,16 @@ async function receive(msg) {
const { data } = msg;
// This request is only made if telemetry is on.
const req = (0, https_1.request)({
hostname: 'api.ionicjs.com',
hostname: 'metrics-capacitor.outsystems.com',
port: 443,
path: '/events/metrics',
path: '/metrics',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
}, (response) => {
debug('Sent %O metric to events service (status: %O)', data.name, response.statusCode);
if (response.statusCode !== 204) {
if (response.statusCode !== 202) {
response.on('data', (chunk) => {
debug('Bad response from events service. Request body: %O', chunk.toString());
});

View file

@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.migrateCommand = void 0;
exports.setAllStringIn = exports.migrateCommand = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
@ -497,6 +497,7 @@ function setAllStringIn(data, start, end, replacement) {
}
return result;
}
exports.setAllStringIn = setAllStringIn;
async function updateAndroidManifest(filename) {
const txt = readFile(filename);
if (!txt) {

View file

@ -85,6 +85,9 @@ async function runCommand(config, selectedPlatformName, options) {
}
}
catch (e) {
if (options.liveReload) {
await livereload_1.CapLiveReloadHelper.revertCapConfigForLiveReload();
}
if (!(0, errors_1.isFatal)(e)) {
(0, errors_1.fatal)((_c = e.stack) !== null && _c !== void 0 ? _c : e);
}

View file

@ -1,9 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sendMetric = exports.telemetryAction = void 0;
exports.getIOSPackageManager = exports.sendMetric = exports.telemetryAction = void 0;
const tslib_1 = require("tslib");
const commander_1 = require("commander");
const debug_1 = tslib_1.__importDefault(require("debug"));
const fs_extra_1 = require("fs-extra");
const colors_1 = tslib_1.__importDefault(require("./colors"));
const ipc_1 = require("./ipc");
const log_1 = require("./log");
@ -51,8 +52,10 @@ function telemetryAction(config, action) {
error: error ? (error.message ? error.message : String(error)) : null,
node_version: process.version,
os: config.cli.os,
ios_package_manager: await getIOSPackageManager(config),
...Object.fromEntries(versions),
};
debug('metric payload: %O', data);
if ((0, term_1.isInteractive)()) {
let sysconfig = await (0, sysconfig_1.readConfig)();
if (!error && typeof sysconfig.telemetry === 'undefined') {
@ -88,6 +91,24 @@ async function sendMetric(sysconfig, name, data) {
}
}
exports.sendMetric = sendMetric;
/**
* Resolve the iOS dependency manager for telemetry. Returns 'unknown' for
* non-iOS projects or when detection throws, so callers never have to handle
* errors from this signal.
*/
async function getIOSPackageManager(config) {
try {
if (!(await (0, fs_extra_1.pathExists)(config.ios.platformDirAbs))) {
return 'unknown';
}
return await config.ios.packageManager;
}
catch (e) {
debug('Could not resolve iOS package manager for telemetry: %O', e);
return 'unknown';
}
}
exports.getIOSPackageManager = getIOSPackageManager;
/**
* Get a unique anonymous identifier for this app.
*/

View file

@ -45,12 +45,12 @@ exports.generatePackageFile = generatePackageFile;
async function checkPluginsForPackageSwift(config, plugins) {
const iOSCapacitorPlugins = plugins.filter((p) => (0, plugin_1.getPluginType)(p, 'ios') === 0 /* PluginType.Core */);
const packageSwiftPluginList = await pluginsWithPackageSwift(iOSCapacitorPlugins);
if (plugins.length == packageSwiftPluginList.length) {
log_1.logger.debug(`Found ${plugins.length} iOS plugins, ${packageSwiftPluginList.length} have a Package.swift file`);
log_1.logger.info('All plugins have a Package.swift file and will be included in Package.swift');
if (iOSCapacitorPlugins.length == packageSwiftPluginList.length) {
log_1.logger.debug(`Found ${iOSCapacitorPlugins.length} Capacitor iOS plugins, ${packageSwiftPluginList.length} have a Package.swift file`);
log_1.logger.info('All Capacitor plugins have a Package.swift file and will be included in Package.swift');
}
else {
log_1.logger.warn('Some installed packages are not compatable with SPM');
log_1.logger.warn('Some installed Capacitor plugins are not compatible with SPM');
}
return packageSwiftPluginList;
}
@ -84,11 +84,12 @@ async function removeCocoapodsFiles(config) {
}
exports.removeCocoapodsFiles = removeCocoapodsFiles;
async function generatePackageText(config, plugins) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
const iosPlatformVersion = await (0, common_1.getCapacitorPackageVersion)(config, config.ios.name);
const iosVersion = (0, common_2.getMajoriOSVersion)(config);
const packageTraits = (_d = (_c = (_b = (_a = config.app.extConfig.experimental) === null || _a === void 0 ? void 0 : _a.ios) === null || _b === void 0 ? void 0 : _b.spm) === null || _c === void 0 ? void 0 : _c.packageTraits) !== null && _d !== void 0 ? _d : {};
const swiftToolsVersion = (_h = (_g = (_f = (_e = config.app.extConfig.experimental) === null || _e === void 0 ? void 0 : _e.ios) === null || _f === void 0 ? void 0 : _f.spm) === null || _g === void 0 ? void 0 : _g.swiftToolsVersion) !== null && _h !== void 0 ? _h : '5.9';
const packageOptions = (_h = (_g = (_f = (_e = config.app.extConfig.experimental) === null || _e === void 0 ? void 0 : _e.ios) === null || _f === void 0 ? void 0 : _f.spm) === null || _g === void 0 ? void 0 : _g.packageOptions) !== null && _h !== void 0 ? _h : {};
const swiftToolsVersion = (_m = (_l = (_k = (_j = config.app.extConfig.experimental) === null || _j === void 0 ? void 0 : _j.ios) === null || _k === void 0 ? void 0 : _k.spm) === null || _l === void 0 ? void 0 : _l.swiftToolsVersion) !== null && _m !== void 0 ? _m : '5.9';
let packageSwiftText = `// swift-tools-version: ${swiftToolsVersion}
import PackageDescription
@ -105,10 +106,28 @@ let package = Package(
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "${iosPlatformVersion}")`;
for (const plugin of plugins) {
if ((0, plugin_1.getPluginType)(plugin, config.ios.name) === 1 /* PluginType.Cordova */) {
packageSwiftText += `,\n .package(name: "${plugin.name}", path: "../../capacitor-cordova-ios-plugins/sources/${plugin.name}")`;
const platformTag = (0, plugin_1.getPluginPlatform)(plugin, config.ios.name);
if ((_o = platformTag.$) === null || _o === void 0 ? void 0 : _o.package) {
const relPath = (0, path_1.relative)(config.ios.nativeXcodeProjDirAbs, plugin.rootPath);
packageSwiftText += `,\n .package(name: "${plugin.id}", path: "${relPath}")`;
}
else {
const sourceFiles = (0, plugin_1.getPlatformElement)(plugin, config.ios.name, 'source-file');
const headerFiles = (0, plugin_1.getPlatformElement)(plugin, config.ios.name, 'header-file');
if (sourceFiles.length === 0 && headerFiles.length === 0) {
continue;
}
packageSwiftText += `,\n .package(name: "${plugin.name}", path: "../../capacitor-cordova-ios-plugins/sources/${plugin.name}")`;
}
}
else {
const relPath = (0, path_1.relative)(config.ios.nativeXcodeProjDirAbs, plugin.rootPath);
const options = packageOptions[plugin.id];
const symlink = options === null || options === void 0 ? void 0 : options.symlink;
const symlinkFolder = (0, path_1.join)('symlinks', plugin.name);
const relPath = symlink ? symlinkFolder : (0, path_1.relative)(config.ios.nativeXcodeProjDirAbs, plugin.rootPath);
if (symlink) {
await (0, fs_extra_1.ensureSymlink)(plugin.rootPath, (0, path_1.resolve)(config.ios.nativeProjectDirAbs, 'CapApp-SPM', symlinkFolder));
}
const traits = packageTraits[plugin.id];
const traitsSuffix = (traits === null || traits === void 0 ? void 0 : traits.length)
? `, traits: [${traits
@ -118,7 +137,7 @@ let package = Package(
})
.join(', ')}]`
: '';
packageSwiftText += `,\n .package(name: "${(_j = plugin.ios) === null || _j === void 0 ? void 0 : _j.name}", path: "${relPath}"${traitsSuffix})`;
packageSwiftText += `,\n .package(name: "${(_p = plugin.ios) === null || _p === void 0 ? void 0 : _p.name}", path: "${relPath}"${traitsSuffix})`;
}
}
packageSwiftText += `
@ -130,7 +149,29 @@ let package = Package(
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm")`;
for (const plugin of plugins) {
packageSwiftText += `,\n .product(name: "${(_k = plugin.ios) === null || _k === void 0 ? void 0 : _k.name}", package: "${(_l = plugin.ios) === null || _l === void 0 ? void 0 : _l.name}")`;
const aliases = Object.entries((_r = (_q = packageOptions[plugin.id]) === null || _q === void 0 ? void 0 : _q.moduleAliases) !== null && _r !== void 0 ? _r : {});
const aliasText = (aliases === null || aliases === void 0 ? void 0 : aliases.length)
? `, moduleAliases: [${aliases
.map(([target, replacement]) => {
return `"${target}": "${replacement}"`;
})
.join(', ')}]`
: '';
let pluginText = `,\n .product(name: "${(_s = plugin.ios) === null || _s === void 0 ? void 0 : _s.name}", package: "${(_t = plugin.ios) === null || _t === void 0 ? void 0 : _t.name}"${aliasText})`;
if ((0, plugin_1.getPluginType)(plugin, config.ios.name) === 1 /* PluginType.Cordova */) {
const platformTag = (0, plugin_1.getPluginPlatform)(plugin, config.ios.name);
if ((_u = platformTag.$) === null || _u === void 0 ? void 0 : _u.package) {
pluginText = `,\n .product(name: "${plugin.id}", package: "${plugin.id}")`;
}
else {
const sourceFiles = (0, plugin_1.getPlatformElement)(plugin, config.ios.name, 'source-file');
const headerFiles = (0, plugin_1.getPlatformElement)(plugin, config.ios.name, 'header-file');
if (sourceFiles.length === 0 && headerFiles.length === 0) {
pluginText = '';
}
}
}
packageSwiftText += pluginText;
}
packageSwiftText += `
]

View file

@ -61,7 +61,7 @@ const fse = require('fs-extra')
### ESM
There is also an `fs-extra/esm` import, that supports both default and named exports. However, note that `fs` methods are not included in `fs-extra/esm`; you still need to import `fs` and/or `fs/promises` seperately:
There is also an `fs-extra/esm` import, that supports both default and named exports. However, note that `fs` methods are not included in `fs-extra/esm`; you still need to import `fs` and/or `fs/promises` separately:
```js
import { readFileSync } from 'fs'

View file

@ -10,14 +10,14 @@ const { areIdentical } = require('../util/stat')
async function createLink (srcpath, dstpath) {
let dstStat
try {
dstStat = await fs.lstat(dstpath)
dstStat = await fs.lstat(dstpath, { bigint: true })
} catch {
// ignore error
}
let srcStat
try {
srcStat = await fs.lstat(srcpath)
srcStat = await fs.lstat(srcpath, { bigint: true })
} catch (err) {
err.message = err.message.replace('lstat', 'ensureLink')
throw err
@ -39,11 +39,11 @@ async function createLink (srcpath, dstpath) {
function createLinkSync (srcpath, dstpath) {
let dstStat
try {
dstStat = fs.lstatSync(dstpath)
dstStat = fs.lstatSync(dstpath, { bigint: true })
} catch {}
try {
const srcStat = fs.lstatSync(srcpath)
const srcStat = fs.lstatSync(srcpath, { bigint: true })
if (dstStat && areIdentical(srcStat, dstStat)) return
} catch (err) {
err.message = err.message.replace('lstat', 'ensureLink')

View file

@ -24,18 +24,18 @@ async function createSymlink (srcpath, dstpath, type) {
// (standard symlink behavior) or fall back to cwd if that doesn't exist
let srcStat
if (path.isAbsolute(srcpath)) {
srcStat = await fs.stat(srcpath)
srcStat = await fs.stat(srcpath, { bigint: true })
} else {
const dstdir = path.dirname(dstpath)
const relativeToDst = path.join(dstdir, srcpath)
try {
srcStat = await fs.stat(relativeToDst)
srcStat = await fs.stat(relativeToDst, { bigint: true })
} catch {
srcStat = await fs.stat(srcpath)
srcStat = await fs.stat(srcpath, { bigint: true })
}
}
const dstStat = await fs.stat(dstpath)
const dstStat = await fs.stat(dstpath, { bigint: true })
if (areIdentical(srcStat, dstStat)) return
}
@ -61,18 +61,18 @@ function createSymlinkSync (srcpath, dstpath, type) {
// (standard symlink behavior) or fall back to cwd if that doesn't exist
let srcStat
if (path.isAbsolute(srcpath)) {
srcStat = fs.statSync(srcpath)
srcStat = fs.statSync(srcpath, { bigint: true })
} else {
const dstdir = path.dirname(dstpath)
const relativeToDst = path.join(dstdir, srcpath)
try {
srcStat = fs.statSync(relativeToDst)
srcStat = fs.statSync(relativeToDst, { bigint: true })
} catch {
srcStat = fs.statSync(srcpath)
srcStat = fs.statSync(srcpath, { bigint: true })
}
}
const dstStat = fs.statSync(dstpath)
const dstStat = fs.statSync(dstpath, { bigint: true })
if (areIdentical(srcStat, dstStat)) return
}

View file

@ -101,7 +101,10 @@ async function checkParentPaths (src, srcStat, dest, funcName) {
try {
destStat = await fs.stat(destParent, { bigint: true })
} catch (err) {
if (err.code === 'ENOENT') return
// The destination parent does not exist yet, but a deeper ancestor might
// (e.g. when it is a symlink into the source tree). Keep walking up so the
// self-subdirectory check is not bypassed.
if (err.code === 'ENOENT') return checkParentPaths(src, srcStat, destParent, funcName)
throw err
}
@ -120,7 +123,10 @@ function checkParentPathsSync (src, srcStat, dest, funcName) {
try {
destStat = fs.statSync(destParent, { bigint: true })
} catch (err) {
if (err.code === 'ENOENT') return
// The destination parent does not exist yet, but a deeper ancestor might
// (e.g. when it is a symlink into the source tree). Keep walking up so the
// self-subdirectory check is not bypassed.
if (err.code === 'ENOENT') return checkParentPathsSync(src, srcStat, destParent, funcName)
throw err
}
if (areIdentical(srcStat, destStat)) {

View file

@ -4,30 +4,47 @@ const fs = require('../fs')
const u = require('universalify').fromPromise
async function utimesMillis (path, atime, mtime) {
// if (!HAS_MILLIS_RES) return fs.utimes(path, atime, mtime, callback)
const fd = await fs.open(path, 'r+')
let closeErr = null
let error = null
try {
await fs.futimes(fd, atime, mtime)
} catch (futimesErr) {
error = futimesErr
} finally {
try {
await fs.close(fd)
} catch (e) {
closeErr = e
} catch (closeErr) {
if (!error) error = closeErr
}
}
if (closeErr) {
throw closeErr
if (error) {
throw error
}
}
function utimesMillisSync (path, atime, mtime) {
const fd = fs.openSync(path, 'r+')
fs.futimesSync(fd, atime, mtime)
return fs.closeSync(fd)
let error = null
try {
fs.futimesSync(fd, atime, mtime)
} catch (futimesErr) {
error = futimesErr
} finally {
try {
fs.closeSync(fd)
} catch (closeErr) {
if (!error) error = closeErr
}
}
if (error) {
throw error
}
}
module.exports = {

View file

@ -1,6 +1,6 @@
{
"name": "fs-extra",
"version": "11.3.4",
"version": "11.3.6",
"description": "fs-extra contains methods that aren't included in the vanilla Node.js fs package. Such as recursive mkdir, copy, and remove.",
"engines": {
"node": ">=14.14"

View file

@ -1,6 +1,6 @@
{
"name": "@capacitor/cli",
"version": "8.3.1",
"version": "8.4.1",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team <hi@ionic.io> (https://ionic.io)",

View file

@ -1,6 +1,6 @@
{
"name": "@capacitor/core",
"version": "8.3.1",
"version": "8.4.1",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team <hi@ionic.io> (https://ionic.io)",

View file

@ -1,259 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [2.6.5](https://github.com/webpack/schema-utils/compare/v2.6.4...v2.6.5) (2020-03-11)
### Bug Fixes
* typo ([#88](https://github.com/webpack/schema-utils/issues/88)) ([52fc92b](https://github.com/webpack/schema-utils/commit/52fc92b531b7503b7bbe1bf9b21bfa26dffbc8f5))
### [2.6.4](https://github.com/webpack/schema-utils/compare/v2.6.3...v2.6.4) (2020-01-17)
### Bug Fixes
* change `initialised` to `initialized` ([#87](https://github.com/webpack/schema-utils/issues/87)) ([70f12d3](https://github.com/webpack/schema-utils/commit/70f12d33a8eaa27249bc9c1a27f886724cf91ea7))
### [2.6.3](https://github.com/webpack/schema-utils/compare/v2.6.2...v2.6.3) (2020-01-17)
### Bug Fixes
* prefer the `baseDataPath` option from arguments ([#86](https://github.com/webpack/schema-utils/issues/86)) ([e236859](https://github.com/webpack/schema-utils/commit/e236859e85b28e35e1294f86fc1ff596a5031cea))
### [2.6.2](https://github.com/webpack/schema-utils/compare/v2.6.1...v2.6.2) (2020-01-14)
### Bug Fixes
* better handle Windows absolute paths ([#85](https://github.com/webpack/schema-utils/issues/85)) ([1fa2930](https://github.com/webpack/schema-utils/commit/1fa2930a161e907b9fc53a7233d605910afdb883))
### [2.6.1](https://github.com/webpack/schema-utils/compare/v2.6.0...v2.6.1) (2019-11-28)
### Bug Fixes
* typescript declarations ([#84](https://github.com/webpack/schema-utils/issues/84)) ([89d55a9](https://github.com/webpack/schema-utils/commit/89d55a9a8edfa6a8ac8b112f226bb3154e260319))
## [2.6.0](https://github.com/webpack/schema-utils/compare/v2.5.0...v2.6.0) (2019-11-27)
### Features
* support configuration via title ([#81](https://github.com/webpack/schema-utils/issues/81)) ([afddc10](https://github.com/webpack/schema-utils/commit/afddc109f6891cd37a9f1835d50862d119a072bf))
### Bug Fixes
* typescript definitions ([#70](https://github.com/webpack/schema-utils/issues/70)) ([f38158d](https://github.com/webpack/schema-utils/commit/f38158d6d040e2c701622778ae8122fb26a4f990))
## [2.5.0](https://github.com/webpack/schema-utils/compare/v2.4.1...v2.5.0) (2019-10-15)
### Bug Fixes
* rework format for maxLength, minLength ([#67](https://github.com/webpack/schema-utils/issues/67)) ([0d12259](https://github.com/webpack/schema-utils/commit/0d12259))
* support all cases with one number in range ([#64](https://github.com/webpack/schema-utils/issues/64)) ([7fc8069](https://github.com/webpack/schema-utils/commit/7fc8069))
* typescript definition and export naming ([#69](https://github.com/webpack/schema-utils/issues/69)) ([a435b79](https://github.com/webpack/schema-utils/commit/a435b79))
### Features
* "smart" numbers range ([62fb107](https://github.com/webpack/schema-utils/commit/62fb107))
### [2.4.1](https://github.com/webpack/schema-utils/compare/v2.4.0...v2.4.1) (2019-09-27)
### Bug Fixes
* publish definitions ([#58](https://github.com/webpack/schema-utils/issues/58)) ([1885faa](https://github.com/webpack/schema-utils/commit/1885faa))
## [2.4.0](https://github.com/webpack/schema-utils/compare/v2.3.0...v2.4.0) (2019-09-26)
### Features
* better errors when the `type` keyword doesn't exist ([0988be2](https://github.com/webpack/schema-utils/commit/0988be2))
* support $data reference ([#56](https://github.com/webpack/schema-utils/issues/56)) ([d2f11d6](https://github.com/webpack/schema-utils/commit/d2f11d6))
* types definitions ([#52](https://github.com/webpack/schema-utils/issues/52)) ([facb431](https://github.com/webpack/schema-utils/commit/facb431))
## [2.3.0](https://github.com/webpack/schema-utils/compare/v2.2.0...v2.3.0) (2019-09-26)
### Features
* support `not` keyword ([#53](https://github.com/webpack/schema-utils/issues/53)) ([765f458](https://github.com/webpack/schema-utils/commit/765f458))
## [2.2.0](https://github.com/webpack/schema-utils/compare/v2.1.0...v2.2.0) (2019-09-02)
### Features
* better error output for `oneOf` and `anyOf` ([#48](https://github.com/webpack/schema-utils/issues/48)) ([#50](https://github.com/webpack/schema-utils/issues/50)) ([332242f](https://github.com/webpack/schema-utils/commit/332242f))
## [2.1.0](https://github.com/webpack-contrib/schema-utils/compare/v2.0.1...v2.1.0) (2019-08-07)
### Bug Fixes
* throw error on sparse arrays ([#47](https://github.com/webpack-contrib/schema-utils/issues/47)) ([b85ac38](https://github.com/webpack-contrib/schema-utils/commit/b85ac38))
### Features
* export `ValidateError` ([#46](https://github.com/webpack-contrib/schema-utils/issues/46)) ([ff781d7](https://github.com/webpack-contrib/schema-utils/commit/ff781d7))
### [2.0.1](https://github.com/webpack-contrib/schema-utils/compare/v2.0.0...v2.0.1) (2019-07-18)
### Bug Fixes
* error message for empty object ([#44](https://github.com/webpack-contrib/schema-utils/issues/44)) ([0b4b4a2](https://github.com/webpack-contrib/schema-utils/commit/0b4b4a2))
### [2.0.0](https://github.com/webpack-contrib/schema-utils/compare/v1.0.0...v2.0.0) (2019-07-17)
### BREAKING CHANGES
* drop support for Node.js < 8.9.0
* drop support `errorMessage`, please use `description` for links.
* api was changed, please look documentation.
* error messages was fully rewritten.
<a name="1.0.0"></a>
# [1.0.0](https://github.com/webpack-contrib/schema-utils/compare/v0.4.7...v1.0.0) (2018-08-07)
### Features
* **src:** add support for custom error messages ([#33](https://github.com/webpack-contrib/schema-utils/issues/33)) ([1cbe4ef](https://github.com/webpack-contrib/schema-utils/commit/1cbe4ef))
<a name="0.4.7"></a>
## [0.4.7](https://github.com/webpack-contrib/schema-utils/compare/v0.4.6...v0.4.7) (2018-08-07)
### Bug Fixes
* **src:** `node >= v4.0.0` support ([#32](https://github.com/webpack-contrib/schema-utils/issues/32)) ([cb13dd4](https://github.com/webpack-contrib/schema-utils/commit/cb13dd4))
<a name="0.4.6"></a>
## [0.4.6](https://github.com/webpack-contrib/schema-utils/compare/v0.4.5...v0.4.6) (2018-08-06)
### Bug Fixes
* **package:** remove lockfile ([#28](https://github.com/webpack-contrib/schema-utils/issues/28)) ([69f1a81](https://github.com/webpack-contrib/schema-utils/commit/69f1a81))
* **package:** remove unnecessary `webpack` dependency ([#26](https://github.com/webpack-contrib/schema-utils/issues/26)) ([532eaa5](https://github.com/webpack-contrib/schema-utils/commit/532eaa5))
<a name="0.4.5"></a>
## [0.4.5](https://github.com/webpack-contrib/schema-utils/compare/v0.4.4...v0.4.5) (2018-02-13)
### Bug Fixes
* **CHANGELOG:** update broken links ([4483b9f](https://github.com/webpack-contrib/schema-utils/commit/4483b9f))
* **package:** update broken links ([f2494ba](https://github.com/webpack-contrib/schema-utils/commit/f2494ba))
<a name="0.4.4"></a>
## [0.4.4](https://github.com/webpack-contrib/schema-utils/compare/v0.4.3...v0.4.4) (2018-02-13)
### Bug Fixes
* **package:** update `dependencies` ([#22](https://github.com/webpack-contrib/schema-utils/issues/22)) ([3aecac6](https://github.com/webpack-contrib/schema-utils/commit/3aecac6))
<a name="0.4.3"></a>
## [0.4.3](https://github.com/webpack-contrib/schema-utils/compare/v0.4.2...v0.4.3) (2017-12-14)
### Bug Fixes
* **validateOptions:** throw `err` instead of `process.exit(1)` ([#17](https://github.com/webpack-contrib/schema-utils/issues/17)) ([c595eda](https://github.com/webpack-contrib/schema-utils/commit/c595eda))
* **ValidationError:** never return `this` in the ctor ([#16](https://github.com/webpack-contrib/schema-utils/issues/16)) ([c723791](https://github.com/webpack-contrib/schema-utils/commit/c723791))
<a name="0.4.2"></a>
## [0.4.2](https://github.com/webpack-contrib/schema-utils/compare/v0.4.1...v0.4.2) (2017-11-09)
### Bug Fixes
* **validateOptions:** catch `ValidationError` and handle it internally ([#15](https://github.com/webpack-contrib/schema-utils/issues/15)) ([9c5ef5e](https://github.com/webpack-contrib/schema-utils/commit/9c5ef5e))
<a name="0.4.1"></a>
## [0.4.1](https://github.com/webpack-contrib/schema-utils/compare/v0.4.0...v0.4.1) (2017-11-03)
### Bug Fixes
* **ValidationError:** use `Error.captureStackTrace` for `err.stack` handling ([#14](https://github.com/webpack-contrib/schema-utils/issues/14)) ([a6fb974](https://github.com/webpack-contrib/schema-utils/commit/a6fb974))
<a name="0.4.0"></a>
# [0.4.0](https://github.com/webpack-contrib/schema-utils/compare/v0.3.0...v0.4.0) (2017-10-28)
### Features
* add support for `typeof`, `instanceof` (`{Function\|RegExp}`) ([#10](https://github.com/webpack-contrib/schema-utils/issues/10)) ([9f01816](https://github.com/webpack-contrib/schema-utils/commit/9f01816))
<a name="0.3.0"></a>
# [0.3.0](https://github.com/webpack-contrib/schema-utils/compare/v0.2.1...v0.3.0) (2017-04-29)
### Features
* add ValidationError ([#8](https://github.com/webpack-contrib/schema-utils/issues/8)) ([d48f0fb](https://github.com/webpack-contrib/schema-utils/commit/d48f0fb))
<a name="0.2.1"></a>
## [0.2.1](https://github.com/webpack-contrib/schema-utils/compare/v0.2.0...v0.2.1) (2017-03-13)
### Bug Fixes
* Include .babelrc to `files` ([28f0363](https://github.com/webpack-contrib/schema-utils/commit/28f0363))
* Include source to `files` ([43b0f2f](https://github.com/webpack-contrib/schema-utils/commit/43b0f2f))
<a name="0.2.0"></a>
# [0.2.0](https://github.com/webpack-contrib/schema-utils/compare/v0.1.0...v0.2.0) (2017-03-12)
<a name="0.1.0"></a>
# 0.1.0 (2017-03-07)
### Features
* **validations:** add validateOptions module ([ae9b47b](https://github.com/webpack-contrib/schema-utils/commit/ae9b47b))
# Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

View file

@ -1,20 +0,0 @@
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,276 +0,0 @@
<div align="center">
<a href="http://json-schema.org">
<img width="160" height="160"
src="https://raw.githubusercontent.com/webpack-contrib/schema-utils/master/.github/assets/logo.png">
</a>
<a href="https://github.com/webpack/webpack">
<img width="200" height="200"
src="https://webpack.js.org/assets/icon-square-big.svg">
</a>
</div>
[![npm][npm]][npm-url]
[![node][node]][node-url]
[![deps][deps]][deps-url]
[![tests][tests]][tests-url]
[![coverage][cover]][cover-url]
[![chat][chat]][chat-url]
[![size][size]][size-url]
# schema-utils
Package for validate options in loaders and plugins.
## Getting Started
To begin, you'll need to install `schema-utils`:
```console
npm install schema-utils
```
## API
**schema.json**
```json
{
"type": "object",
"properties": {
"option": {
"type": ["boolean"]
}
},
"additionalProperties": false
}
```
```js
import schema from './path/to/schema.json';
import validate from 'schema-utils';
const options = { option: true };
const configuration = { name: 'Loader Name/Plugin Name/Name' };
validate(schema, options, configuration);
```
### `schema`
Type: `String`
JSON schema.
Simple example of schema:
```json
{
"type": "object",
"properties": {
"name": {
"description": "This is description of option.",
"type": "string"
}
},
"additionalProperties": false
}
```
### `options`
Type: `Object`
Object with options.
```js
validate(
schema,
{
name: 123,
},
{ name: 'MyPlugin' }
);
```
### `configuration`
Allow to configure validator.
There is an alternative method to configure the `name` and`baseDataPath` options via the `title` property in the schema.
For example:
```json
{
"title": "My Loader options",
"type": "object",
"properties": {
"name": {
"description": "This is description of option.",
"type": "string"
}
},
"additionalProperties": false
}
```
The last word used for the `baseDataPath` option, other words used for the `name` option.
Based on the example above the `name` option equals `My Loader`, the `baseDataPath` option equals `options`.
#### `name`
Type: `Object`
Default: `"Object"`
Allow to setup name in validation errors.
```js
validate(schema, options, { name: 'MyPlugin' });
```
```shell
Invalid configuration object. MyPlugin has been initialised using a configuration object that does not match the API schema.
- configuration.optionName should be a integer.
```
#### `baseDataPath`
Type: `String`
Default: `"configuration"`
Allow to setup base data path in validation errors.
```js
validate(schema, options, { name: 'MyPlugin', baseDataPath: 'options' });
```
```shell
Invalid options object. MyPlugin has been initialised using an options object that does not match the API schema.
- options.optionName should be a integer.
```
#### `postFormatter`
Type: `Function`
Default: `undefined`
Allow to reformat errors.
```js
validate(schema, options, {
name: 'MyPlugin',
postFormatter: (formattedError, error) => {
if (error.keyword === 'type') {
return `${formattedError}\nAdditional Information.`;
}
return formattedError;
},
});
```
```shell
Invalid options object. MyPlugin has been initialized using an options object that does not match the API schema.
- options.optionName should be a integer.
Additional Information.
```
## Examples
**schema.json**
```json
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"test": {
"anyOf": [
{ "type": "array" },
{ "type": "string" },
{ "instanceof": "RegExp" }
]
},
"transform": {
"instanceof": "Function"
},
"sourceMap": {
"type": "boolean"
}
},
"additionalProperties": false
}
```
### `Loader`
```js
import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';
import schema from 'path/to/schema.json';
function loader(src, map) {
const options = getOptions(this) || {};
validateOptions(schema, options, {
name: 'Loader Name',
baseDataPath: 'options',
});
// Code...
}
export default loader;
```
### `Plugin`
```js
import validateOptions from 'schema-utils';
import schema from 'path/to/schema.json';
class Plugin {
constructor(options) {
validateOptions(schema, options, {
name: 'Plugin Name',
baseDataPath: 'options',
});
this.options = options;
}
apply(compiler) {
// Code...
}
}
export default Plugin;
```
## Contributing
Please take a moment to read our contributing guidelines if you haven't yet done so.
[CONTRIBUTING](./.github/CONTRIBUTING.md)
## License
[MIT](./LICENSE)
[npm]: https://img.shields.io/npm/v/schema-utils.svg
[npm-url]: https://npmjs.com/package/schema-utils
[node]: https://img.shields.io/node/v/schema-utils.svg
[node-url]: https://nodejs.org
[deps]: https://david-dm.org/webpack/schema-utils.svg
[deps-url]: https://david-dm.org/webpack/schema-utils
[tests]: https://dev.azure.com/webpack/schema-utils/_apis/build/status/webpack.schema-utils?branchName=master
[tests-url]: https://dev.azure.com/webpack/schema-utils/_build/latest?definitionId=9&branchName=master
[cover]: https://codecov.io/gh/webpack/schema-utils/branch/master/graph/badge.svg
[cover-url]: https://codecov.io/gh/webpack/schema-utils
[chat]: https://badges.gitter.im/webpack/webpack.svg
[chat-url]: https://gitter.im/webpack/webpack
[size]: https://packagephobia.now.sh/badge?p=schema-utils
[size-url]: https://packagephobia.now.sh/result?p=schema-utils

View file

@ -1,115 +0,0 @@
export default ValidationError;
export type JSONSchema6 = import('json-schema').JSONSchema6;
export type JSONSchema7 = import('json-schema').JSONSchema7;
export type Schema =
| import('json-schema').JSONSchema4
| import('json-schema').JSONSchema6
| import('json-schema').JSONSchema7;
export type ValidationErrorConfiguration = {
name?: string | undefined;
baseDataPath?: string | undefined;
postFormatter?: import('./validate').PostFormatter | undefined;
};
export type PostFormatter = (
formattedError: string,
error: import('ajv').ErrorObject & {
children?: import('ajv').ErrorObject[] | undefined;
}
) => string;
export type SchemaUtilErrorObject = import('ajv').ErrorObject & {
children?: import('ajv').ErrorObject[] | undefined;
};
export type SPECIFICITY = number;
declare class ValidationError extends Error {
/**
* @param {Array<SchemaUtilErrorObject>} errors
* @param {Schema} schema
* @param {ValidationErrorConfiguration} configuration
*/
constructor(
errors: (import('ajv').ErrorObject & {
children?: import('ajv').ErrorObject[] | undefined;
})[],
schema:
| import('json-schema').JSONSchema4
| import('json-schema').JSONSchema6
| import('json-schema').JSONSchema7,
configuration?: import('./validate').ValidationErrorConfiguration
);
/** @type {Array<SchemaUtilErrorObject>} */
errors: Array<SchemaUtilErrorObject>;
/** @type {Schema} */
schema: Schema;
/** @type {string} */
headerName: string;
/** @type {string} */
baseDataPath: string;
/** @type {PostFormatter | null} */
postFormatter: PostFormatter | null;
/**
* @param {string} path
* @returns {Schema}
*/
getSchemaPart(
path: string
):
| import('json-schema').JSONSchema4
| import('json-schema').JSONSchema6
| import('json-schema').JSONSchema7;
/**
* @param {Schema} schema
* @param {Array<Object>} prevSchemas
* @returns {string}
*/
formatSchema(
schema:
| import('json-schema').JSONSchema4
| import('json-schema').JSONSchema6
| import('json-schema').JSONSchema7,
prevSchemas?: Object[]
): string;
/**
* @param {Schema=} schemaPart
* @param {(boolean | Array<string>)=} additionalPath
* @param {boolean=} needDot
* @returns {string}
*/
getSchemaPartText(
schemaPart?:
| import('json-schema').JSONSchema4
| import('json-schema').JSONSchema6
| import('json-schema').JSONSchema7
| undefined,
additionalPath?: boolean | string[] | undefined,
needDot?: boolean | undefined
): string;
/**
* @param {Schema=} schemaPart
* @returns {string}
*/
getSchemaPartDescription(
schemaPart?:
| import('json-schema').JSONSchema4
| import('json-schema').JSONSchema6
| import('json-schema').JSONSchema7
| undefined
): string;
/**
* @param {SchemaUtilErrorObject} error
* @returns {string}
*/
formatValidationError(
error: import('ajv').ErrorObject & {
children?: import('ajv').ErrorObject[] | undefined;
}
): string;
/**
* @param {Array<SchemaUtilErrorObject>} errors
* @returns {string}
*/
formatValidationErrors(
errors: (import('ajv').ErrorObject & {
children?: import('ajv').ErrorObject[] | undefined;
})[]
): string;
}

View file

@ -1,2 +0,0 @@
declare const _exports: typeof import('./validate').default;
export = _exports;

View file

@ -1,13 +0,0 @@
export default addAbsolutePathKeyword;
export type Ajv = import('ajv').Ajv;
export type SchemaUtilErrorObject = import('ajv').ErrorObject & {
children?: import('ajv').ErrorObject[] | undefined;
};
/**
*
* @param {Ajv} ajv
* @returns {Ajv}
*/
declare function addAbsolutePathKeyword(
ajv: import('ajv').Ajv
): import('ajv').Ajv;

View file

@ -1,43 +0,0 @@
export = Range;
/**
* @typedef {[number, boolean]} RangeValue
*/
/**
* @callback RangeValueCallback
* @param {RangeValue} rangeValue
* @returns {boolean}
*/
declare class Range {
/** @type {Array<RangeValue>} */
_left: Array<RangeValue>;
/** @type {Array<RangeValue>} */
_right: Array<RangeValue>;
/**
* @param {number} value
* @param {boolean=} exclusive
*/
left(value: number, exclusive?: boolean | undefined): void;
/**
* @param {number} value
* @param {boolean=} exclusive
*/
right(value: number, exclusive?: boolean | undefined): void;
/**
* @param {boolean} logic is not logic applied
* @return {string} "smart" range string representation
*/
format(logic?: boolean): string;
}
declare namespace Range {
export {
getOperator,
formatRight,
formatLeft,
formatRange,
getRangeValue,
RangeValue,
RangeValueCallback,
};
}
type RangeValue = [number, boolean];
type RangeValueCallback = (rangeValue: [number, boolean]) => boolean;

View file

@ -1,43 +0,0 @@
export default validate;
export type JSONSchema4 = import('json-schema').JSONSchema4;
export type JSONSchema6 = import('json-schema').JSONSchema6;
export type JSONSchema7 = import('json-schema').JSONSchema7;
export type ErrorObject = Ajv.ErrorObject;
export type Schema =
| import('json-schema').JSONSchema4
| import('json-schema').JSONSchema6
| import('json-schema').JSONSchema7;
export type SchemaUtilErrorObject = Ajv.ErrorObject & {
children?: Ajv.ErrorObject[] | undefined;
};
export type PostFormatter = (
formattedError: string,
error: Ajv.ErrorObject & {
children?: Ajv.ErrorObject[] | undefined;
}
) => string;
export type ValidationErrorConfiguration = {
name?: string | undefined;
baseDataPath?: string | undefined;
postFormatter?: PostFormatter | undefined;
};
/**
* @param {Schema} schema
* @param {Array<object> | object} options
* @param {ValidationErrorConfiguration=} configuration
* @returns {void}
*/
declare function validate(
schema:
| import('json-schema').JSONSchema4
| import('json-schema').JSONSchema6
| import('json-schema').JSONSchema7,
options: any,
configuration?: ValidationErrorConfiguration | undefined
): void;
declare namespace validate {
export { ValidationError };
export { ValidationError as ValidateError };
}
import Ajv from 'ajv';
import ValidationError from './ValidationError';

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
"use strict";
const validate = require('./validate');
module.exports = validate.default;

View file

@ -1,91 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
/** @typedef {import("ajv").Ajv} Ajv */
/** @typedef {import("../validate").SchemaUtilErrorObject} SchemaUtilErrorObject */
/**
*
* @param {string} data
* @param {object} schema
* @param {string} message
* @returns {object} // Todo `returns` should be `SchemaUtilErrorObject`
*/
function errorMessage(message, schema, data) {
return {
keyword: 'absolutePath',
params: {
absolutePath: data
},
message,
parentSchema: schema
};
}
/**
* @param {boolean} shouldBeAbsolute
* @param {object} schema
* @param {string} data
* @returns {object}
*/
function getErrorFor(shouldBeAbsolute, schema, data) {
const message = shouldBeAbsolute ? `The provided value ${JSON.stringify(data)} is not an absolute path!` : `A relative path is expected. However, the provided value ${JSON.stringify(data)} is an absolute path!`;
return errorMessage(message, schema, data);
}
/**
*
* @param {Ajv} ajv
* @returns {Ajv}
*/
function addAbsolutePathKeyword(ajv) {
ajv.addKeyword('absolutePath', {
errors: true,
type: 'string',
compile(schema, parentSchema) {
/**
* @param {any} data
* @returns {boolean}
*/
function callback(data) {
let passes = true;
const isExclamationMarkPresent = data.includes('!');
if (isExclamationMarkPresent) {
callback.errors = [errorMessage(`The provided value ${JSON.stringify(data)} contains exclamation mark (!) which is not allowed because it's reserved for loader syntax.`, parentSchema, data)];
passes = false;
} // ?:[A-Za-z]:\\ - Windows absolute path
// \\\\ - Windows network absolute path
// \/ - Unix-like OS absolute path
const isCorrectAbsolutePath = schema === /^(?:[A-Za-z]:(\\|\/)|\\\\|\/)/.test(data);
if (!isCorrectAbsolutePath) {
callback.errors = [getErrorFor(schema, parentSchema, data)];
passes = false;
}
return passes;
}
/** @type {null | Array<SchemaUtilErrorObject>}*/
callback.errors = [];
return callback;
}
});
return ajv;
}
var _default = addAbsolutePathKeyword;
exports.default = _default;

View file

@ -1,163 +0,0 @@
"use strict";
/**
* @typedef {[number, boolean]} RangeValue
*/
/**
* @callback RangeValueCallback
* @param {RangeValue} rangeValue
* @returns {boolean}
*/
class Range {
/**
* @param {"left" | "right"} side
* @param {boolean} exclusive
* @returns {">" | ">=" | "<" | "<="}
*/
static getOperator(side, exclusive) {
if (side === 'left') {
return exclusive ? '>' : '>=';
}
return exclusive ? '<' : '<=';
}
/**
* @param {number} value
* @param {boolean} logic is not logic applied
* @param {boolean} exclusive is range exclusive
* @returns {string}
*/
static formatRight(value, logic, exclusive) {
if (logic === false) {
return Range.formatLeft(value, !logic, !exclusive);
}
return `should be ${Range.getOperator('right', exclusive)} ${value}`;
}
/**
* @param {number} value
* @param {boolean} logic is not logic applied
* @param {boolean} exclusive is range exclusive
* @returns {string}
*/
static formatLeft(value, logic, exclusive) {
if (logic === false) {
return Range.formatRight(value, !logic, !exclusive);
}
return `should be ${Range.getOperator('left', exclusive)} ${value}`;
}
/**
* @param {number} start left side value
* @param {number} end right side value
* @param {boolean} startExclusive is range exclusive from left side
* @param {boolean} endExclusive is range exclusive from right side
* @param {boolean} logic is not logic applied
* @returns {string}
*/
static formatRange(start, end, startExclusive, endExclusive, logic) {
let result = 'should be';
result += ` ${Range.getOperator(logic ? 'left' : 'right', logic ? startExclusive : !startExclusive)} ${start} `;
result += logic ? 'and' : 'or';
result += ` ${Range.getOperator(logic ? 'right' : 'left', logic ? endExclusive : !endExclusive)} ${end}`;
return result;
}
/**
* @param {Array<RangeValue>} values
* @param {boolean} logic is not logic applied
* @return {RangeValue} computed value and it's exclusive flag
*/
static getRangeValue(values, logic) {
let minMax = logic ? Infinity : -Infinity;
let j = -1;
const predicate = logic ?
/** @type {RangeValueCallback} */
([value]) => value <= minMax :
/** @type {RangeValueCallback} */
([value]) => value >= minMax;
for (let i = 0; i < values.length; i++) {
if (predicate(values[i])) {
[minMax] = values[i];
j = i;
}
}
if (j > -1) {
return values[j];
}
return [Infinity, true];
}
constructor() {
/** @type {Array<RangeValue>} */
this._left = [];
/** @type {Array<RangeValue>} */
this._right = [];
}
/**
* @param {number} value
* @param {boolean=} exclusive
*/
left(value, exclusive = false) {
this._left.push([value, exclusive]);
}
/**
* @param {number} value
* @param {boolean=} exclusive
*/
right(value, exclusive = false) {
this._right.push([value, exclusive]);
}
/**
* @param {boolean} logic is not logic applied
* @return {string} "smart" range string representation
*/
format(logic = true) {
const [start, leftExclusive] = Range.getRangeValue(this._left, logic);
const [end, rightExclusive] = Range.getRangeValue(this._right, !logic);
if (!Number.isFinite(start) && !Number.isFinite(end)) {
return '';
}
const realStart = leftExclusive ? start + 1 : start;
const realEnd = rightExclusive ? end - 1 : end; // e.g. 5 < x < 7, 5 < x <= 6, 6 <= x <= 6
if (realStart === realEnd) {
return `should be ${logic ? '' : '!'}= ${realStart}`;
} // e.g. 4 < x < ∞
if (Number.isFinite(start) && !Number.isFinite(end)) {
return Range.formatLeft(start, logic, leftExclusive);
} // e.g. ∞ < x < 4
if (!Number.isFinite(start) && Number.isFinite(end)) {
return Range.formatRight(end, logic, rightExclusive);
}
return Range.formatRange(start, end, leftExclusive, rightExclusive, logic);
}
}
module.exports = Range;

View file

@ -1,154 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _ajv = _interopRequireDefault(require("ajv"));
var _ajvKeywords = _interopRequireDefault(require("ajv-keywords"));
var _absolutePath = _interopRequireDefault(require("./keywords/absolutePath"));
var _ValidationError = _interopRequireDefault(require("./ValidationError"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/** @typedef {import("json-schema").JSONSchema4} JSONSchema4 */
/** @typedef {import("json-schema").JSONSchema6} JSONSchema6 */
/** @typedef {import("json-schema").JSONSchema7} JSONSchema7 */
/** @typedef {import("ajv").ErrorObject} ErrorObject */
/** @typedef {(JSONSchema4 | JSONSchema6 | JSONSchema7)} Schema */
/** @typedef {ErrorObject & { children?: Array<ErrorObject>}} SchemaUtilErrorObject */
/**
* @callback PostFormatter
* @param {string} formattedError
* @param {SchemaUtilErrorObject} error
* @returns {string}
*/
/**
* @typedef {Object} ValidationErrorConfiguration
* @property {string=} name
* @property {string=} baseDataPath
* @property {PostFormatter=} postFormatter
*/
const ajv = new _ajv.default({
allErrors: true,
verbose: true,
$data: true,
coerceTypes: true
});
(0, _ajvKeywords.default)(ajv, ['instanceof', 'formatMinimum', 'formatMaximum', 'patternRequired']); // Custom keywords
(0, _absolutePath.default)(ajv);
/**
* @param {Schema} schema
* @param {Array<object> | object} options
* @param {ValidationErrorConfiguration=} configuration
* @returns {void}
*/
function validate(schema, options, configuration) {
let errors = [];
if (Array.isArray(options)) {
errors = Array.from(options).map(nestedOptions => validateObject(schema, nestedOptions));
errors.forEach((list, idx) => {
const applyPrefix =
/**
* @param {SchemaUtilErrorObject} error
*/
error => {
// eslint-disable-next-line no-param-reassign
error.dataPath = `[${idx}]${error.dataPath}`;
if (error.children) {
error.children.forEach(applyPrefix);
}
};
list.forEach(applyPrefix);
});
errors = errors.reduce((arr, items) => arr.concat(items), []);
} else {
errors = validateObject(schema, options);
}
if (errors.length > 0) {
throw new _ValidationError.default(errors, schema, configuration);
}
}
/**
* @param {Schema} schema
* @param {Array<object> | object} options
* @returns {Array<SchemaUtilErrorObject>}
*/
function validateObject(schema, options) {
const compiledSchema = ajv.compile(schema);
const valid = compiledSchema(options);
if (!compiledSchema.errors) {
return [];
}
return valid ? [] : filterErrors(compiledSchema.errors);
}
/**
* @param {Array<ErrorObject>} errors
* @returns {Array<SchemaUtilErrorObject>}
*/
function filterErrors(errors) {
/** @type {Array<SchemaUtilErrorObject>} */
let newErrors = [];
for (const error of
/** @type {Array<SchemaUtilErrorObject>} */
errors) {
const {
dataPath
} = error;
/** @type {Array<SchemaUtilErrorObject>} */
let children = [];
newErrors = newErrors.filter(oldError => {
if (oldError.dataPath.includes(dataPath)) {
if (oldError.children) {
children = children.concat(oldError.children.slice(0));
} // eslint-disable-next-line no-undefined, no-param-reassign
oldError.children = undefined;
children.push(oldError);
return false;
}
return true;
});
if (children.length) {
error.children = children;
}
newErrors.push(error);
}
return newErrors;
} // TODO change after resolve https://github.com/microsoft/TypeScript/issues/34994
validate.ValidationError = _ValidationError.default;
validate.ValidateError = _ValidationError.default;
var _default = validate;
exports.default = _default;

View file

@ -1,78 +0,0 @@
{
"name": "@develar/schema-utils",
"version": "2.6.5",
"description": "webpack Validation Utils",
"license": "MIT",
"repository": "webpack/schema-utils",
"author": "webpack Contrib (https://github.com/webpack-contrib)",
"homepage": "https://github.com/webpack/schema-utils",
"bugs": "https://github.com/webpack/schema-utils/issues",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"main": "dist/index.js",
"types": "declarations/index.d.ts",
"engines": {
"node": ">= 8.9.0"
},
"scripts": {
"start": "npm run build -- -w",
"clean": "del-cli dist declarations",
"prebuild": "npm run clean",
"build:types": "tsc --declaration --emitDeclarationOnly --outDir declarations && prettier \"declarations/**/*.ts\" --write",
"build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files",
"build": "npm-run-all -p \"build:**\"",
"commitlint": "commitlint --from=master",
"security": "npm audit",
"lint:prettier": "prettier \"{**/*,*}.{js,json,md,yml,css,ts}\" --list-different",
"lint:js": "eslint --cache .",
"lint:types": "tsc --pretty --noEmit",
"lint": "npm-run-all -l -p \"lint:**\"",
"test:only": "cross-env NODE_ENV=test jest",
"test:watch": "npm run test:only -- --watch",
"test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
"pretest": "npm run lint",
"test": "npm run test:coverage",
"prepare": "npm run build",
"release": "standard-version",
"defaults": "webpack-defaults"
},
"files": [
"dist",
"declarations"
],
"dependencies": {
"ajv": "^6.12.0",
"ajv-keywords": "^3.4.1"
},
"devDependencies": {
"@babel/cli": "^7.8.3",
"@babel/core": "^7.8.3",
"@babel/preset-env": "^7.8.3",
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@types/json-schema": "^7.0.4",
"@webpack-contrib/defaults": "^6.3.0",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^24.9.0",
"commitlint-azure-pipelines-cli": "^1.0.3",
"cross-env": "^6.0.3",
"del": "^5.1.0",
"del-cli": "^3.0.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.9.0",
"eslint-plugin-import": "^2.20.0",
"husky": "^4.0.10",
"jest": "^24.9.0",
"jest-junit": "^10.0.0",
"lint-staged": "^9.5.0",
"npm-run-all": "^4.1.5",
"prettier": "^1.19.1",
"standard-version": "^7.0.1",
"typescript": "^3.7.5"
},
"keywords": [
"webpack"
]
}

View file

@ -0,0 +1,96 @@
# @electron-internal/extract-zip
> [!WARNING]
> **Internal to the Electron project.** This package exists to serve Electron's
> own tooling. Use from non-Electron packages is not supported: the API may
> change to suit Electron's needs, and bug reports or feature requests from
> outside use cases may be closed without action. If you need a general-purpose
> extractor, use [`extract-zip`](https://github.com/max-mapper/extract-zip).
Fast, safe, native zip extraction for Node.js. Drop-in replacement for [`extract-zip`](https://github.com/max-mapper/extract-zip).
- **Native**: Rust core via N-API, decompression runs off the event loop.
- **Fast**: ~2x faster on entry-heavy archives, never slower. See [benchmarks](#benchmarks).
- **Safe**: hardened against Zip Slip, symlink escapes, absolute paths, NUL injection, Windows reserved names, and zip bombs.
- **Zero runtime deps**: no `debug`/`yauzl`/`get-stream` in your tree.
- **Cross-platform**: prebuilt binaries for macOS, Linux (glibc/musl), and Windows (x64/arm64).
## Install
```sh
yarn add @electron-internal/extract-zip
```
## Usage
ESM only:
```js
import extract from '@electron-internal/extract-zip';
await extract('archive.zip', { dir: '/absolute/output/path' });
```
`dir` (required, absolute) is the only option. Everything else is fixed: existing
files are overwritten, archive mode bits (masked to `0o777`) and mtimes are
preserved, symlinks are created (skipped on Windows without symlink privilege),
and writes are parallelised across `min(cpus, 8)` workers. The original's
`onEntry`, `defaultDirMode`, and `defaultFileMode` are not supported, since no
consumer in the `electron` org uses them.
## Security
Every entry path is verified to land inside `dir`:
- `..` traversal is rejected and absolute paths are stripped, via the `zip` crate's audited `enclosed_name()`.
- Directories are created one component at a time without following symlinks; an entry whose path crosses a symlink is rejected.
- Symlinks are created after all files. Each target is walked against the on-disk tree and the archive's own symlink set, with relative-only hops bounded by `dir` and a hop cap, so a chain resolving outside `dir` is rejected before any link is created.
- NUL bytes and Windows reserved device names (`CON`, `AUX`, `COM1`, trailing space/dot) are rejected on every platform.
- Symlink targets are capped at 4 KiB; per-file output is capped at `max(2 x declared size, 1 MB)` to catch entries that lie about their size.
`test/security.test.js` exercises these escapes end-to-end with hand-crafted archives.
## Benchmarks
`yarn bench` (Apple M-series, Node 24, median of 5):
| Corpus | Zip size | `extract-zip` (JS) | this | Speedup |
|---|--:|--:|--:|--:|
| electron-v42.2.0-darwin-arm64 | 112 MB | 817 ms | 441 ms | 1.9x |
| 8 x 4 MB compressible | 0.1 MB | 24 ms | 3 ms | 9.4x |
| 2000 small text files | 0.4 MB | 372 ms | 199 ms | 1.9x |
| 200 incompressible files | 6.2 MB | 40 ms | 22 ms | 1.8x |
| `node_modules` | 2.9 MB | 68 ms | 37 ms | 1.9x |
Extraction runs in phases: validate paths and create directories, inflate and
write files in parallel with zlib-ng, then apply symlinks and directory
metadata. The Electron number is gated by its single 182 MB framework binary,
which can't be split further.
## Distribution
One package ships all prebuilt binaries (~2 MB gzipped): macOS
`darwin-universal`, Windows `x64`/`arm64`, and Linux `x64`/`arm64` for both glibc
and musl. `binding.js` picks the right one at load time. No
`optionalDependencies`, no postinstall, no network at install.
## Building from source
Requires a Rust toolchain (and `cmake` for zlib-ng).
```sh
yarn install
yarn build # builds index.<your-platform>.node
yarn test
```
## Releasing
Releases are driven by [semantic-release](https://semantic-release.gitbook.io/)
on every push to `main`: conventional commit messages decide the version bump,
CI builds all targets, and the fat package is published to npm via trusted
publishing. No manual version bumps or tags.
## License
BSD-2-Clause

View file

@ -0,0 +1,8 @@
/* auto-generated by NAPI-RS */
/* eslint-disable */
export declare function extract(zipPath: string, opts: ExtractOptions): Promise<void>
export interface ExtractOptions {
/** Destination directory. Must be an absolute path. */
dir: string
}

View file

@ -0,0 +1,629 @@
// prettier-ignore
/* eslint-disable */
// @ts-nocheck
/* auto-generated by NAPI-RS */
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const __dirname = new URL('.', import.meta.url).pathname
const { readFileSync } = require('node:fs')
let nativeBinding = null
const loadErrors = []
const isMusl = () => {
let musl = false
if (process.platform === 'linux') {
musl = isMuslFromFilesystem()
if (musl === null) {
musl = isMuslFromReport()
}
if (musl === null) {
musl = isMuslFromChildProcess()
}
}
return musl
}
const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-')
const isMuslFromFilesystem = () => {
try {
return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')
} catch {
return null
}
}
const isMuslFromReport = () => {
let report = null
if (typeof process.report?.getReport === 'function') {
process.report.excludeNetwork = true
report = process.report.getReport()
}
if (!report) {
return null
}
if (report.header && report.header.glibcVersionRuntime) {
return false
}
if (Array.isArray(report.sharedObjects)) {
if (report.sharedObjects.some(isFileMusl)) {
return true
}
}
return false
}
const isMuslFromChildProcess = () => {
try {
return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')
} catch (e) {
// If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false
return false
}
}
function requireNative() {
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
try {
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH);
} catch (err) {
loadErrors.push(err)
}
} else if (process.platform === 'android') {
if (process.arch === 'arm64') {
try {
return require('./index.android-arm64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-android-arm64 is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-android-arm64/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm') {
try {
return require('./index.android-arm-eabi.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-android-arm-eabi is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-android-arm-eabi/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`))
}
} else if (process.platform === 'win32') {
if (process.arch === 'x64') {
if (process.config?.variables?.shlib_suffix === 'dll.a' || process.config?.variables?.node_target_type === 'shared_library') {
try {
return require('./index.win32-x64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-win32-x64-gnu is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-win32-x64-gnu/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./index.win32-x64-msvc.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-win32-x64-msvc is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-win32-x64-msvc/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'ia32') {
try {
return require('./index.win32-ia32-msvc.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-win32-ia32-msvc is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-win32-ia32-msvc/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm64') {
try {
return require('./index.win32-arm64-msvc.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-win32-arm64-msvc is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-win32-arm64-msvc/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`))
}
} else if (process.platform === 'darwin') {
try {
return require('./index.darwin-universal.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-darwin-universal is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-darwin-universal/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
if (process.arch === 'x64') {
try {
return require('./index.darwin-x64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-darwin-x64 is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-darwin-x64/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm64') {
try {
return require('./index.darwin-arm64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-darwin-arm64 is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-darwin-arm64/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`))
}
} else if (process.platform === 'freebsd') {
if (process.arch === 'x64') {
try {
return require('./index.freebsd-x64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-freebsd-x64 is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-freebsd-x64/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm64') {
try {
return require('./index.freebsd-arm64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-freebsd-arm64 is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-freebsd-arm64/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`))
}
} else if (process.platform === 'linux') {
if (process.arch === 'x64') {
if (isMusl()) {
try {
return require('./index.linux-x64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-x64-musl is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-x64-musl/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./index.linux-x64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-x64-gnu is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-x64-gnu/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
try {
return require('./index.linux-x64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-x64-musl is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-x64-musl/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'arm64') {
if (isMusl()) {
try {
return require('./index.linux-arm64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-arm64-musl is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-arm64-musl/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./index.linux-arm64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-arm64-gnu is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-arm64-gnu/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
try {
return require('./index.linux-arm64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-arm64-musl is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-arm64-musl/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'arm') {
if (isMusl()) {
try {
return require('./index.linux-arm-musleabihf.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-arm-musleabihf is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-arm-musleabihf/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./index.linux-arm-gnueabihf.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-arm-gnueabihf is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-arm-gnueabihf/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'loong64') {
if (isMusl()) {
try {
return require('./index.linux-loong64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-loong64-musl is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-loong64-musl/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./index.linux-loong64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-loong64-gnu is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-loong64-gnu/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'riscv64') {
if (isMusl()) {
try {
return require('./index.linux-riscv64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-riscv64-musl is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-riscv64-musl/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./index.linux-riscv64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-riscv64-gnu is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-riscv64-gnu/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'ppc64') {
try {
return require('./index.linux-ppc64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-ppc64-gnu is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-ppc64-gnu/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 's390x') {
try {
return require('./index.linux-s390x-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-s390x-gnu is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-s390x-gnu/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'ia32') {
try {
return require('./index.linux-ia32-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-ia32-gnu is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-linux-ia32-gnu/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`))
}
} else if (process.platform === 'openharmony') {
if (process.arch === 'arm64') {
try {
return require('./index.openharmony-arm64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-openharmony-arm64 is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-openharmony-arm64/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'x64') {
try {
return require('./index.openharmony-x64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-openharmony-x64 is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-openharmony-x64/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm') {
try {
return require('./index.openharmony-arm.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-openharmony-arm is intentionally not published)') })()
const bindingPackageVersion = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-openharmony-arm/package.json is intentionally not published)') })().version
if (bindingPackageVersion !== '0.0.0-development' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.0-development but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`))
}
} else {
loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`))
}
}
nativeBinding = requireNative()
if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
let wasiBinding = null
let wasiBindingError = null
try {
wasiBinding = require('./index.wasi.cjs')
nativeBinding = wasiBinding
} catch (err) {
if (process.env.NAPI_RS_FORCE_WASI) {
wasiBindingError = err
}
}
if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
try {
wasiBinding = (() => { throw new Error('prebuild for this platform is not bundled (and @electron-internal/extract-zip-wasm32-wasi is intentionally not published)') })()
nativeBinding = wasiBinding
} catch (err) {
if (process.env.NAPI_RS_FORCE_WASI) {
if (!wasiBindingError) {
wasiBindingError = err
} else {
wasiBindingError.cause = err
}
loadErrors.push(err)
}
}
}
if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) {
const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error')
error.cause = wasiBindingError
throw error
}
}
if (!nativeBinding) {
if (loadErrors.length > 0) {
throw new Error(
`Cannot find native binding. ` +
`npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` +
'Please try `npm i` again after removing both package-lock.json and node_modules directory.',
{
cause: loadErrors.reduce((err, cur) => {
cur.cause = err
return cur
}),
},
)
}
throw new Error(`Failed to load native binding`)
}
const { extract } = nativeBinding
export { extract }

View file

@ -0,0 +1,8 @@
export interface ExtractOptions {
/** Destination directory. Must be an absolute path. */
dir: string;
}
export declare function extract(zipPath: string, opts: ExtractOptions): Promise<void>;
export default extract;

Binary file not shown.

View file

@ -0,0 +1,23 @@
import path from 'node:path';
import { extract as nativeExtract } from './binding.js';
/**
* Extract a zip archive to a directory.
*
* await extract(source, { dir: '/abs/path' })
*
* @param {string} zipPath
* @param {import('./index.js').ExtractOptions} opts
* @returns {Promise<void>}
*/
export async function extract(zipPath, opts) {
if (!opts || typeof opts.dir !== 'string') {
throw new TypeError('extract: opts.dir is required');
}
if (!path.isAbsolute(opts.dir)) {
throw new TypeError('extract: opts.dir must be an absolute path');
}
return nativeExtract(zipPath, opts);
}
export default extract;

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