From ae78da6fc1875d65363eac0921c6ca06773a5241 Mon Sep 17 00:00:00 2001 From: Lzebulon Date: Sat, 24 May 2025 21:58:08 +0200 Subject: [PATCH 01/77] initial setup wikimedia for test --- flake.lock | 53 ++++++++++--------- flake.nix | 7 ++- hosts/vm/wiki/default.nix | 16 ++++++ hosts/vm/wiki/hardware-configuration.nix | 45 ++++++++++++++++ hosts/vm/wiki/networking.nix | 65 ++++++++++++++++++++++++ modules/services/wiki.nix | 44 ++++++++++++++++ 6 files changed, 204 insertions(+), 26 deletions(-) create mode 100644 hosts/vm/wiki/default.nix create mode 100644 hosts/vm/wiki/hardware-configuration.nix create mode 100644 hosts/vm/wiki/networking.nix create mode 100644 modules/services/wiki.nix diff --git a/flake.lock b/flake.lock index 0be5400..4585f26 100644 --- a/flake.lock +++ b/flake.lock @@ -10,11 +10,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1736955230, - "narHash": "sha256-uenf8fv2eG5bKM8C/UvFaiJMZ4IpUFaQxk9OH5t/1gA=", + "lastModified": 1747575206, + "narHash": "sha256-NwmAFuDUO/PFcgaGGr4j3ozG9Pe5hZ/ogitWhY+D81k=", "owner": "ryantm", "repo": "agenix", - "rev": "e600439ec4c273cf11e06fe4d9d906fb98fa097c", + "rev": "4835b1dc898959d8547a871ef484930675cb47f1", "type": "github" }, "original": { @@ -31,11 +31,11 @@ ] }, "locked": { - "lastModified": 1700795494, - "narHash": "sha256-gzGLZSiOhf155FW7262kdHo2YDeugp3VuIFb4/GGng0=", + "lastModified": 1744478979, + "narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=", "owner": "lnl7", "repo": "nix-darwin", - "rev": "4b9b83d5a92e8c1fbfd8eb27eda375908c11ec4d", + "rev": "43975d782b418ebf4969e9ccba82466728c2851b", "type": "github" }, "original": { @@ -50,11 +50,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1736143030, - "narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=", + "lastModified": 1743550720, + "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de", + "rev": "c621e8422220273271f52058f618c94e405bb0f5", "type": "github" }, "original": { @@ -71,11 +71,11 @@ ] }, "locked": { - "lastModified": 1703113217, - "narHash": "sha256-7ulcXOk63TIT2lVDSExj7XzFx09LpdSAPtvgtM7yQPE=", + "lastModified": 1745494811, + "narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=", "owner": "nix-community", "repo": "home-manager", - "rev": "3bfaacf46133c037bb356193bd2f1765d9dc82c1", + "rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be", "type": "github" }, "original": { @@ -86,30 +86,33 @@ }, "nixpkgs": { "locked": { - "lastModified": 1737672001, - "narHash": "sha256-YnHJJ19wqmibLQdUeq9xzE6CjrMA568KN/lFPuSVs4I=", + "lastModified": 1747825515, + "narHash": "sha256-BWpMQymVI73QoKZdcVCxUCCK3GNvr/xa2Dc4DM1o2BE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "035f8c0853c2977b24ffc4d0a42c74f00b182cd8", + "rev": "cd2812de55cf87df88a9e09bf3be1ce63d50c1a6", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-24.11", + "ref": "nixos-25.05", "repo": "nixpkgs", "type": "github" } }, "nixpkgs-lib": { "locked": { - "lastModified": 1735774519, - "narHash": "sha256-CewEm1o2eVAnoqb6Ml+Qi9Gg/EfNAxbRx1lANGVyoLI=", - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz" + "lastModified": 1743296961, + "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", + "type": "github" }, "original": { - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz" + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" } }, "root": { @@ -142,11 +145,11 @@ ] }, "locked": { - "lastModified": 1737483750, - "narHash": "sha256-5An1wq5U8sNycOBBg3nsDDgpwBmR9liOpDGlhliA6Xo=", + "lastModified": 1747469671, + "narHash": "sha256-bo1ptiFoNqm6m1B2iAhJmWCBmqveLVvxom6xKmtuzjg=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "f2cc121df15418d028a59c9737d38e3a90fbaf8f", + "rev": "ab0378b61b0d85e73a8ab05d5c6029b5bd58c9fb", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index b05c87a..85f4454 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "Configuration NixOS du Crans"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; flake-parts.url = "github:hercules-ci/flake-parts"; # Formatter @@ -71,6 +71,11 @@ specialArgs = inputs; modules = [ ./hosts/vm/two ] ++ baseModules; }; + + wiki = nixosSystem { + specialArgs = inputs; + modules = [ ./hosts/vm/wiki ] ++ baseModules; + }; }; }; diff --git a/hosts/vm/wiki/default.nix b/hosts/vm/wiki/default.nix new file mode 100644 index 0000000..f4baa1a --- /dev/null +++ b/hosts/vm/wiki/default.nix @@ -0,0 +1,16 @@ +{ ... }: + +{ + imports = [ + ./hardware-configuration.nix + # ./networking.nix + + ../../../modules + ../../../modules/services/wiki.nix + ]; + + networking.hostName = "wiki"; + boot.loader.grub.devices = [ "/dev/sda" ]; + + system.stateVersion = "25.05"; +} diff --git a/hosts/vm/wiki/hardware-configuration.nix b/hosts/vm/wiki/hardware-configuration.nix new file mode 100644 index 0000000..c61d274 --- /dev/null +++ b/hosts/vm/wiki/hardware-configuration.nix @@ -0,0 +1,45 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ + config, + lib, + pkgs, + modulesPath, + ... +}: + +{ + imports = [ + (modulesPath + "/profiles/qemu-guest.nix") + ]; + + boot.initrd.availableKernelModules = [ + "ata_piix" + "uhci_hcd" + "virtio_pci" + "virtio_scsi" + "sd_mod" + "sr_mod" + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = { + device = "/dev/disk/by-uuid/6aab06d9-2d09-4929-a680-719c6818a663"; + fsType = "ext4"; + }; + + swapDevices = [ { device = "/dev/disk/by-uuid/24f88af8-323d-48e3-8872-402b8bbbdc13"; } ]; + + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking + # (the default) this is the recommended approach. When using systemd-networkd it's + # still possible to use this option, but it's recommended to use it in conjunction + # with explicit per-interface declarations with `networking.interfaces..useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.ens18.useDHCP = lib.mkDefault true; + # networking.interfaces.ens19.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; +} diff --git a/hosts/vm/wiki/networking.nix b/hosts/vm/wiki/networking.nix new file mode 100644 index 0000000..8ec9ca3 --- /dev/null +++ b/hosts/vm/wiki/networking.nix @@ -0,0 +1,65 @@ +{ ... }: + +{ + networking = { + interfaces = { + ens18 = { + + ipv4 = { + addresses = [ + { + address = "172.16.10.139"; + prefixLength = 24; + } + ]; + }; + + ipv6 = { + addresses = [ + { + address = "fd00::10:0:ff:fe01:3910"; + prefixLength = 64; + } + ]; + }; + + }; + + ens19 = { + + ipv4 = { + addresses = [ + { + address = "172.16.3.139"; + prefixLength = 24; + } + ]; + routes = [ + { + address = "0.0.0.0"; + via = "172.16.3.99"; + prefixLength = 0; + } + ]; + }; + + ipv6 = { + addresses = [ + { + address = "2a0c:700:3::ff:fe01:3903"; + prefixLength = 64; + } + ]; + routes = [ + { + address = "::"; + via = "2a0c:700:3::ff:fe00:9903"; + prefixLength = 0; + } + ]; + }; + + }; + }; + }; +} diff --git a/modules/services/wiki.nix b/modules/services/wiki.nix new file mode 100644 index 0000000..c692a0d --- /dev/null +++ b/modules/services/wiki.nix @@ -0,0 +1,44 @@ +{pkgs, ...}: +{ + + services.mediawiki = { + enable = true; + + httpd.virtualHost = { + hostName = "test-wiki.crans.org"; + adminAddr = "root@crans.org"; + + listen = [ + { + ip = "127.0.0.1"; + port = 8080; + # ssl = false; + } + ]; + }; + + passwordFile = pkgs.writeText "password" "cardbotine"; + + extraConfig = '' + # contact info + $wgEmergencyContact = 'contact@crans.org'; + + + + # default theme + $wgDefaultSkin = 'citizen'; + + $wgLanguageCode = 'fr'; + + # Disable anonymous editing + $wgGroupPermissions['*']['edit'] = false; + ''; + + skins = { + Citizen = pkgs.fetchzip { + url = "https://github.com/StarCitizenTools/mediawiki-skins-Citizen/archive/main.zip"; + sha256 = "sha256-6ZhrAHAKWHGij1qBgigEll0xbGhEOo0hYTMuakkU06o="; + }; + }; + }; +} From 3eba630fe97c57e50b1598f56d130a66c7fd19fb Mon Sep 17 00:00:00 2001 From: Lzebulon Date: Sat, 24 May 2025 22:35:19 +0200 Subject: [PATCH 02/77] fix name and networking --- flake.nix | 10 +-- hosts/vm/{wiki => mediakiwi}/default.nix | 4 +- .../hardware-configuration.nix | 0 hosts/vm/mediakiwi/networking.nix | 29 +++++++++ hosts/vm/wiki/networking.nix | 65 ------------------- 5 files changed, 36 insertions(+), 72 deletions(-) rename hosts/vm/{wiki => mediakiwi}/default.nix (78%) rename hosts/vm/{wiki => mediakiwi}/hardware-configuration.nix (100%) create mode 100644 hosts/vm/mediakiwi/networking.nix delete mode 100644 hosts/vm/wiki/networking.nix diff --git a/flake.nix b/flake.nix index 85f4454..58af87f 100644 --- a/flake.nix +++ b/flake.nix @@ -51,6 +51,11 @@ specialArgs = inputs; modules = [ ./hosts/vm/livre ] ++ baseModules; }; + + mediakiwi = nixosSystem { + specialArgs = inputs; + modules = [ ./hosts/vm/medakiwi ] ++ baseModules; + }; neo = nixosSystem { specialArgs = inputs; @@ -71,11 +76,6 @@ specialArgs = inputs; modules = [ ./hosts/vm/two ] ++ baseModules; }; - - wiki = nixosSystem { - specialArgs = inputs; - modules = [ ./hosts/vm/wiki ] ++ baseModules; - }; }; }; diff --git a/hosts/vm/wiki/default.nix b/hosts/vm/mediakiwi/default.nix similarity index 78% rename from hosts/vm/wiki/default.nix rename to hosts/vm/mediakiwi/default.nix index f4baa1a..44ee90d 100644 --- a/hosts/vm/wiki/default.nix +++ b/hosts/vm/mediakiwi/default.nix @@ -3,13 +3,13 @@ { imports = [ ./hardware-configuration.nix - # ./networking.nix + ./networking.nix ../../../modules ../../../modules/services/wiki.nix ]; - networking.hostName = "wiki"; + networking.hostName = "mediakiwi"; boot.loader.grub.devices = [ "/dev/sda" ]; system.stateVersion = "25.05"; diff --git a/hosts/vm/wiki/hardware-configuration.nix b/hosts/vm/mediakiwi/hardware-configuration.nix similarity index 100% rename from hosts/vm/wiki/hardware-configuration.nix rename to hosts/vm/mediakiwi/hardware-configuration.nix diff --git a/hosts/vm/mediakiwi/networking.nix b/hosts/vm/mediakiwi/networking.nix new file mode 100644 index 0000000..d4accf4 --- /dev/null +++ b/hosts/vm/mediakiwi/networking.nix @@ -0,0 +1,29 @@ +{ ... }: + +{ + networking = { + interfaces = { + ens18 = { + + ipv4 = { + addresses = [ + { + address = "172.16.10.144"; + prefixLength = 24; + } + ]; + }; + + ipv6 = { + addresses = [ + { + address = "fd00::10:0:ff:fe01:4410"; + prefixLength = 64; + } + ]; + }; + + }; + }; + }; +} diff --git a/hosts/vm/wiki/networking.nix b/hosts/vm/wiki/networking.nix deleted file mode 100644 index 8ec9ca3..0000000 --- a/hosts/vm/wiki/networking.nix +++ /dev/null @@ -1,65 +0,0 @@ -{ ... }: - -{ - networking = { - interfaces = { - ens18 = { - - ipv4 = { - addresses = [ - { - address = "172.16.10.139"; - prefixLength = 24; - } - ]; - }; - - ipv6 = { - addresses = [ - { - address = "fd00::10:0:ff:fe01:3910"; - prefixLength = 64; - } - ]; - }; - - }; - - ens19 = { - - ipv4 = { - addresses = [ - { - address = "172.16.3.139"; - prefixLength = 24; - } - ]; - routes = [ - { - address = "0.0.0.0"; - via = "172.16.3.99"; - prefixLength = 0; - } - ]; - }; - - ipv6 = { - addresses = [ - { - address = "2a0c:700:3::ff:fe01:3903"; - prefixLength = 64; - } - ]; - routes = [ - { - address = "::"; - via = "2a0c:700:3::ff:fe00:9903"; - prefixLength = 0; - } - ]; - }; - - }; - }; - }; -} From ac9ed3a369404a318c13dfd03c59e0a6a59249a6 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sat, 24 May 2025 23:18:35 +0200 Subject: [PATCH 03/77] Ajout de la conf hardware --- hosts/vm/mediakiwi/default.nix | 2 +- hosts/vm/mediakiwi/hardware-configuration.nix | 34 +++++--------- modules/services/mediawiki.nix | 41 +++++++++++++++++ modules/services/wiki.nix | 44 ------------------- 4 files changed, 52 insertions(+), 69 deletions(-) create mode 100644 modules/services/mediawiki.nix delete mode 100644 modules/services/wiki.nix diff --git a/hosts/vm/mediakiwi/default.nix b/hosts/vm/mediakiwi/default.nix index 44ee90d..09e1ac4 100644 --- a/hosts/vm/mediakiwi/default.nix +++ b/hosts/vm/mediakiwi/default.nix @@ -6,7 +6,7 @@ ./networking.nix ../../../modules - ../../../modules/services/wiki.nix + # ../../../modules/services/mediawiki.nix ]; networking.hostName = "mediakiwi"; diff --git a/hosts/vm/mediakiwi/hardware-configuration.nix b/hosts/vm/mediakiwi/hardware-configuration.nix index c61d274..de32983 100644 --- a/hosts/vm/mediakiwi/hardware-configuration.nix +++ b/hosts/vm/mediakiwi/hardware-configuration.nix @@ -1,37 +1,24 @@ # Do not modify this file! It was generated by ‘nixos-generate-config’ # and may be overwritten by future invocations. Please make changes # to /etc/nixos/configuration.nix instead. -{ - config, - lib, - pkgs, - modulesPath, - ... -}: +{ config, lib, pkgs, modulesPath, ... }: { - imports = [ - (modulesPath + "/profiles/qemu-guest.nix") - ]; + imports = + [ (modulesPath + "/profiles/qemu-guest.nix") + ]; - boot.initrd.availableKernelModules = [ - "ata_piix" - "uhci_hcd" - "virtio_pci" - "virtio_scsi" - "sd_mod" - "sr_mod" - ]; + boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "virtio_scsi" "sd_mod" "sr_mod" ]; boot.initrd.kernelModules = [ ]; boot.kernelModules = [ ]; boot.extraModulePackages = [ ]; - fileSystems."/" = { - device = "/dev/disk/by-uuid/6aab06d9-2d09-4929-a680-719c6818a663"; - fsType = "ext4"; - }; + fileSystems."/" = + { device = "/dev/disk/by-uuid/74148438-bd6e-4c19-a41c-d20c907f1fc1"; + fsType = "ext4"; + }; - swapDevices = [ { device = "/dev/disk/by-uuid/24f88af8-323d-48e3-8872-402b8bbbdc13"; } ]; + swapDevices = [ ]; # Enables DHCP on each ethernet and wireless interface. In case of scripted networking # (the default) this is the recommended approach. When using systemd-networkd it's @@ -39,7 +26,6 @@ # with explicit per-interface declarations with `networking.interfaces..useDHCP`. networking.useDHCP = lib.mkDefault true; # networking.interfaces.ens18.useDHCP = lib.mkDefault true; - # networking.interfaces.ens19.useDHCP = lib.mkDefault true; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; } diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix new file mode 100644 index 0000000..bfdca9b --- /dev/null +++ b/modules/services/mediawiki.nix @@ -0,0 +1,41 @@ +{ pkgs, ... }: + +{ + services.mediawiki = { + enable = true; + + httpd.virtualHost = { + hostName = "mediakiwi.crans.org"; + adminAddr = "root@crans.org"; + + listen = [ + { + ip = "127.0.0.1"; + port = 8080; + } + ]; + }; + + passwordFile = pkgs.writeText "password" "cardbotine"; + + extraConfig = '' + # contact info + $wgEmergencyContact = 'contact@crans.org'; + + # default theme + $wgDefaultSkin = 'citizen'; + + $wgLanguageCode = 'fr'; + + # Disable anonymous editing + $wgGroupPermissions['*']['edit'] = false; + ''; + + skins = { + Citizen = pkgs.fetchzip { + url = "https://github.com/StarCitizenTools/mediawiki-skins-Citizen/archive/main.zip"; + sha256 = "sha256-6ZhrAHAKWHGij1qBgigEll0xbGhEOo0hYTMuakkU06o="; + }; + }; + }; +} diff --git a/modules/services/wiki.nix b/modules/services/wiki.nix deleted file mode 100644 index c692a0d..0000000 --- a/modules/services/wiki.nix +++ /dev/null @@ -1,44 +0,0 @@ -{pkgs, ...}: -{ - - services.mediawiki = { - enable = true; - - httpd.virtualHost = { - hostName = "test-wiki.crans.org"; - adminAddr = "root@crans.org"; - - listen = [ - { - ip = "127.0.0.1"; - port = 8080; - # ssl = false; - } - ]; - }; - - passwordFile = pkgs.writeText "password" "cardbotine"; - - extraConfig = '' - # contact info - $wgEmergencyContact = 'contact@crans.org'; - - - - # default theme - $wgDefaultSkin = 'citizen'; - - $wgLanguageCode = 'fr'; - - # Disable anonymous editing - $wgGroupPermissions['*']['edit'] = false; - ''; - - skins = { - Citizen = pkgs.fetchzip { - url = "https://github.com/StarCitizenTools/mediawiki-skins-Citizen/archive/main.zip"; - sha256 = "sha256-6ZhrAHAKWHGij1qBgigEll0xbGhEOo0hYTMuakkU06o="; - }; - }; - }; -} From f8a26eead31e0bec6b3f7792a94d6069c560c12d Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sat, 24 May 2025 23:24:31 +0200 Subject: [PATCH 04/77] =?UTF-8?q?Boooouuuuuh,=20lz=20a=20oubli=C3=A9=20un?= =?UTF-8?q?=20i?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 58af87f..f643f82 100644 --- a/flake.nix +++ b/flake.nix @@ -54,7 +54,7 @@ mediakiwi = nixosSystem { specialArgs = inputs; - modules = [ ./hosts/vm/medakiwi ] ++ baseModules; + modules = [ ./hosts/vm/mediakiwi ] ++ baseModules; }; neo = nixosSystem { From b627adf9fabcd2feec402e23e29829cd4ccb7897 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sat, 24 May 2025 23:42:54 +0200 Subject: [PATCH 05/77] =?UTF-8?q?ajout=20de=20la=20cl=C3=A9=20ssh=20de=20l?= =?UTF-8?q?a=20cl=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- secrets.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/secrets.nix b/secrets.nix index 70877b7..a9c1816 100644 --- a/secrets.nix +++ b/secrets.nix @@ -23,6 +23,7 @@ let cephiroth = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOsBGkhiu6l3jeo15cQHMu3dPyL025zXPV2ZH02EDYEt root@nixos"; jitsi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB6jVMIZ5y2oXX9HOkw7r5UUjw95MlFaFuu7FnEC0Q8z root@jitsi"; livre = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEVfKNokHG6ig32hhQxTep+fKFmKahlDClPrX/dP4/gb root@livre"; + mediakiwi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIlW9V//EDarZmfXOZo8qKQ9g1bAzDO1c/0X+paXDXUB root@iso"; neo = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMGfSvxqC2PJYRrxJaivVDujwlwCZ6AwH8hOSA9ktZ1V root@neo"; redite = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOwfVmR3NjZf6qkDlTSiyo39Up5nSNUVW7jYDWXrY8Xr root@redite"; thot = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFKNg1b8ft1L55+joXQ/7Dt2QTOdkea8opTEnq4xrhPU root@thot"; @@ -34,6 +35,7 @@ let cephiroth jitsi livre + mediakiwi neo redite thot @@ -46,6 +48,7 @@ let "cephiroth" "jitsi" "livre" + "mediakiwi" "neo" "redite" "thot" @@ -59,6 +62,7 @@ let cephiroth jitsi livre + mediakiwi neo thot two From 887355948a1ec656018cf90dedad479d24a56931 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sat, 24 May 2025 23:46:49 +0200 Subject: [PATCH 06/77] ajout des secrets --- secrets/restic/mediakiwi/base-password.age | Bin 0 -> 1125 bytes secrets/restic/mediakiwi/base-repo.age | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 secrets/restic/mediakiwi/base-password.age create mode 100644 secrets/restic/mediakiwi/base-repo.age diff --git a/secrets/restic/mediakiwi/base-password.age b/secrets/restic/mediakiwi/base-password.age new file mode 100644 index 0000000000000000000000000000000000000000..00a6901fddcf834703d0957172ea52582ae67a73 GIT binary patch literal 1125 zcmZ9|>x&Dn^>(w-AlQwLewn>|`P1+=VBn|43G)?+S+NNn9 zWh#g=H^yYc7q?B^z)lcPPCXy&!*D9xag5Useh{Z@+z^q;Idq%2s>F@XJ!I}cWFo^ouNJ638joVVS)Oic=T^R7nQw zm3lG3v%*YRDVlXVPS~NWnoY>nYyu7FzBEJODv&f_8;!&_B%ZW+5cGytijSuU4NNj= z)liI<2{@Y<(@;`zS%0dgW^p~F6ty_7hpS6dpMn8^IXYEbUGfXcy?^FmH`@EP4kzsmKqjn#SF`_I#S0K zAqtB;Z<2`!+Za@%HPP-h8ihPfrhR-0ffdA!_erTBDmmA-^)!#NT|iIp!z>Mm`wO(!`1MtNguX zRefpy+@!p;=feZz`nii=FI+$M(@JG>`n~vzG`g|(S|;EuPqk7l2r+v4qT zQwPRFZv}2FuJBWLo&gne+dC`&>^|0c_RyZmhh6}L2k(3TqrK&C4&MHJbo|?gXLp(7 z2Z1-6qZ4mT|9s@Pn^!(t$bLL~T0JuV_|f6r?-%vuzkhXpIRDzo%JQGbKRCKJxK({% jG(Yz4V(Hr6 ssh-ed25519 Q4BaLw P2+KLPkdP6J/jap6h5CjSZrI810Q+fQewfz4sVeKbxs +sscwySXFJkPBkD+Q8Qr/O5PIdg8BpekRbZvSFCwqzJU +-> piv-p256 ewCc3w A/KZMD15TYm2PT7D5UhO/ccBYfnLjWt04YuGNO/zmVVF +PJMQkFaRJeYeeZm5YvLaBDJEnxxu1s1Ic2dk/XF7saw +-> piv-p256 6CL/Pw A8JeaVfx3zUB9te4QpdvhmhR2LJv2PVGBplObOAqZcb6 +D0ILtI+GjzjMFoUJNTpxyoTIk71pi35vmM1bnnWH8ds +-> ssh-ed25519 I2EdxQ 7bZ77qNvZIsY7S+MqJ5Ly7sfOF8Gza03AOgzXSlQ7Xg +52FuZGam9btEm7emPfXooxbdlTM0gRUAvWp5w6+4a9U +-> ssh-ed25519 J/iReg jy+TvvTo0BHzOXdymp8PVgm2m1mfp6bLzorOlekZBgE +ycXE/lo77N0fONiNU7Po0SkOKm0SoNRhFpSSi+oHN+Q +-> ssh-ed25519 GNhSGw TiskT2pR9GG+7Wy2G7ROHsNVb9qk6b4zrqSe5RpRd08 +xWX7nHGccOUfETmQq8dm29FPhJ9VUUlAZh8PiWkB8uA +-> ssh-ed25519 eXMAtA 3kd9Reigr7mMtTHZ9+cE6i1D4gXRKSn3BWIowOwa2UY +TEWJn35/cJs39iHNrW9FPv6sWS1Lm5VwTmNHQzQl868 +--- j8GLHzJZbXtyK0X0ndh4sgzSK6daJDPuXtLFxIuSAM0 +"U#>>y@/iO5(G[V)|U2P+l+B>>У0 4ՅI+£ +s[ukmۉ_FiFo?bb,>?Ylc=T%yC+e*lyT#vvO \ No newline at end of file From 183b08e6b075f739e672117a0007ca9804de86b1 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sat, 24 May 2025 23:48:52 +0200 Subject: [PATCH 07/77] ajout des bons users --- secrets/restic/mediakiwi/base-repo.age | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/secrets/restic/mediakiwi/base-repo.age b/secrets/restic/mediakiwi/base-repo.age index 6c75405..f11d129 100644 --- a/secrets/restic/mediakiwi/base-repo.age +++ b/secrets/restic/mediakiwi/base-repo.age @@ -1,18 +1,18 @@ age-encryption.org/v1 --> ssh-ed25519 Q4BaLw P2+KLPkdP6J/jap6h5CjSZrI810Q+fQewfz4sVeKbxs -sscwySXFJkPBkD+Q8Qr/O5PIdg8BpekRbZvSFCwqzJU --> piv-p256 ewCc3w A/KZMD15TYm2PT7D5UhO/ccBYfnLjWt04YuGNO/zmVVF -PJMQkFaRJeYeeZm5YvLaBDJEnxxu1s1Ic2dk/XF7saw --> piv-p256 6CL/Pw A8JeaVfx3zUB9te4QpdvhmhR2LJv2PVGBplObOAqZcb6 -D0ILtI+GjzjMFoUJNTpxyoTIk71pi35vmM1bnnWH8ds --> ssh-ed25519 I2EdxQ 7bZ77qNvZIsY7S+MqJ5Ly7sfOF8Gza03AOgzXSlQ7Xg -52FuZGam9btEm7emPfXooxbdlTM0gRUAvWp5w6+4a9U --> ssh-ed25519 J/iReg jy+TvvTo0BHzOXdymp8PVgm2m1mfp6bLzorOlekZBgE -ycXE/lo77N0fONiNU7Po0SkOKm0SoNRhFpSSi+oHN+Q --> ssh-ed25519 GNhSGw TiskT2pR9GG+7Wy2G7ROHsNVb9qk6b4zrqSe5RpRd08 -xWX7nHGccOUfETmQq8dm29FPhJ9VUUlAZh8PiWkB8uA --> ssh-ed25519 eXMAtA 3kd9Reigr7mMtTHZ9+cE6i1D4gXRKSn3BWIowOwa2UY -TEWJn35/cJs39iHNrW9FPv6sWS1Lm5VwTmNHQzQl868 ---- j8GLHzJZbXtyK0X0ndh4sgzSK6daJDPuXtLFxIuSAM0 -"U#>>y@/iO5(G[V)|U2P+l+B>>У0 4ՅI+£ -s[ukmۉ_FiFo?bb,>?Ylc=T%yC+e*lyT#vvO \ No newline at end of file +-> ssh-ed25519 Q4BaLw sE36D+xmCV+/+9g931TX6WaAPW9DxMOwmi90OuocjHI +q2YgqncgDC7/V7nSZEi3lx6kvgDpJeZlZo05sN8v6NM +-> piv-p256 ewCc3w A2lMDz4LJuNkQ8xlb70glZ/xtdm0r2JEU4vTQ36ogy/V +4zqYME53mxNfFhO+P2rH7OyN262U0Q/nrZWZJ40hc+I +-> piv-p256 6CL/Pw A7E9lfpNRlzE06ZV5H7WoUyPXRyDqv9jY54VtdFQqQkB +PjvFsXYJQzIDPcDQQhfdd4byDEwxWsoN96tnQGkCMAs +-> ssh-ed25519 I2EdxQ kJbICcrfjuzQpTCJWQV1NqviGTnhPHU8mzuRPSAX0zk +vKqpdOKeT9SDHnANVPCX9RsiGA1dLp4WuQshl3uYfAo +-> ssh-ed25519 J/iReg s6ubCGFE5xEKU4gZyB0oqkjV6JkCVZKp7IyzfJMdRRk +MGbb2QdkpzMJ9KYu4nHD1QMeVbbmVBZ3Ye4Hd+rXcog +-> ssh-ed25519 GNhSGw j2XeY4L1W4v6xy19wuqCZWQImw6FPXFvIHBMc3x2cwA +Wb/ibMUhYYDTQDVP50wM3gK5J6EfSJ+FNJ/1zspOYzU +-> ssh-ed25519 eXMAtA NHdvoWubAs9/6kF1Sr+2+EyoDXcfGdKcKErkGOZPHGk +SGvZCyPWpBHxi/GHgBMOrM9LMPqcAwRzoSGI+Gna2qc +--- 6VydlzKjJtIaRnblQalhEwKwjBbPL4drY4cc8sSdfhY +'UL|E5-.ǿV8#jz. [àj +?eېz>gڐ]cfLri-آm揦0ς)z]lMIA'/:~4CT8 \ No newline at end of file From 65106534c0b828d07fc41722a7ff23a921d7e91a Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sun, 25 May 2025 00:54:57 +0200 Subject: [PATCH 08/77] =?UTF-8?q?M=C3=A0j=20des=20secrets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- secrets.nix | 2 +- secrets/acme/env.age | 38 ++++++++++----------- secrets/apprentix/root.age | 32 ++++++++--------- secrets/common/root.age | Bin 1451 -> 1561 bytes secrets/restic/apprentix/base-password.age | Bin 1125 -> 1125 bytes secrets/restic/apprentix/base-repo.age | Bin 979 -> 979 bytes secrets/restic/client_env.age | Bin 1525 -> 1635 bytes secrets/restic/jitsi/base-password.age | Bin 1125 -> 1125 bytes secrets/restic/jitsi/base-repo.age | Bin 971 -> 971 bytes secrets/restic/livre/base-password.age | Bin 1125 -> 1125 bytes secrets/restic/livre/base-repo.age | 34 +++++++++--------- secrets/restic/mediakiwi/base-password.age | Bin 1125 -> 1125 bytes secrets/restic/mediakiwi/base-repo.age | 33 +++++++++--------- secrets/restic/neo/base-password.age | Bin 1125 -> 1125 bytes secrets/restic/neo/base-repo.age | Bin 967 -> 967 bytes secrets/restic/redite/base-password.age | 34 +++++++++--------- secrets/restic/redite/base-repo.age | Bin 973 -> 973 bytes secrets/restic/two/base-password.age | 37 ++++++++++---------- secrets/restic/two/base-repo.age | Bin 967 -> 967 bytes 19 files changed, 105 insertions(+), 105 deletions(-) diff --git a/secrets.nix b/secrets.nix index a9c1816..09292e5 100644 --- a/secrets.nix +++ b/secrets.nix @@ -23,7 +23,7 @@ let cephiroth = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOsBGkhiu6l3jeo15cQHMu3dPyL025zXPV2ZH02EDYEt root@nixos"; jitsi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB6jVMIZ5y2oXX9HOkw7r5UUjw95MlFaFuu7FnEC0Q8z root@jitsi"; livre = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEVfKNokHG6ig32hhQxTep+fKFmKahlDClPrX/dP4/gb root@livre"; - mediakiwi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIlW9V//EDarZmfXOZo8qKQ9g1bAzDO1c/0X+paXDXUB root@iso"; + mediakiwi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAiCZU+gdUt2jOxR0niVFsNzw0LIleYvwNhMFIANR5YE root@mediakiwi"; neo = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMGfSvxqC2PJYRrxJaivVDujwlwCZ6AwH8hOSA9ktZ1V root@neo"; redite = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOwfVmR3NjZf6qkDlTSiyo39Up5nSNUVW7jYDWXrY8Xr root@redite"; thot = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFKNg1b8ft1L55+joXQ/7Dt2QTOdkea8opTEnq4xrhPU root@thot"; diff --git a/secrets/acme/env.age b/secrets/acme/env.age index 14cc6d0..cb049f8 100644 --- a/secrets/acme/env.age +++ b/secrets/acme/env.age @@ -1,21 +1,19 @@ age-encryption.org/v1 --> ssh-ed25519 iTd7eA SOdizgV+LHwgOVZlLWlW0ywVXHEzmGjBTEAbpbSGBAs -4AO1uVgvA+jkpgKrdFZD0BIMRmxGbKavfYZdpgDh79c --> ssh-ed25519 /Gpyew Tl0BGQR5Y2gEVRT08vx0LOouwZgGeB8w51r8Opt2Mgg -tsmtuQPnV7LM1ZByAsVyDrRwl4TkwQ9qioHR0/dDraw --> piv-p256 ewCc3w A4seXIEIpFDXUUNnAgrnx4vYhbw3ii8lBg9SpWiGJLte -LInyfV4f0sPsJcK4qbzmPQBcyxFimDleNujC8sNSu0k --> piv-p256 6CL/Pw At0UITcGiU6NQ28t3wAHlStnvfuQNvRUg7lOluxRo+a4 -Tw7/nTlZjKVEwsAWrnsFZ61v4S2olIKsYyGbQC0GI7o --> ssh-ed25519 I2EdxQ bFTORPs47D3JQEXa1i1Zv6gm39Jbqh6UwrwWrYtuLnM -jX28nZJ3WbfC30eq1Q0VCSmrBbrm3Nog96aWrxAsI5s --> ssh-ed25519 J/iReg F+xTdDl1YK0XT5NMudO9Oz+tMrOU/CDX85NbTcMxPTs -rTFkioHgePRwCLeKhqeVGYewnxCcgl+05YM6re30WEo --> ssh-ed25519 GNhSGw cMADVhvNtNaMO/bKWpB9riONTiukhJqeNarPa+zuE18 -X2XeVei3FHNwHZ4q+MxYQHkQx4qgjtxVtwRdv1dFo7s --> ssh-ed25519 eXMAtA VjP7gBICaF5BeCYYt9wXhY0WEkEU1SfVvCrl5MmvhDY -s6x46ikGHX15DFuwjo/q3+Ldx/hFriUT9dZ4iVJyG7Q ---- NoHZglVeoiTggX8013Hj4PAlZVFkKOc1kAheXoT5QhE -iFE -dPU0;*1+8`fP1(FN~7/'tMb -Cen&Ux7C+S l4 i]ҙo3OQF>?S>$Fv-UGϯL*ڮbo>˴zeak ssh-ed25519 iTd7eA QPYUyGlkgSU0k3Vc6PmavCYjjAC4DRfkvk6XQLuirw0 +dVysDcxvctcsbZ5cMkU4sHssrdLmnP7TWxs2elE7ijo +-> ssh-ed25519 /Gpyew YxZoLmyF6EA8QChByzrijJv1AvXFZlJzt6g4GHsLbzk +kpa66wZk8pk0a9OYa8coDz+u92UgXBERTq8CxHOacYQ +-> piv-p256 ewCc3w AyYUxdelZxaqJtrX/X4yUHg+PrdWG7477yBWY0lnWYBh +G1OKPryIaaQ+VPSo8JoQVXsQstCL+Jie8eP0ORQ51TI +-> piv-p256 6CL/Pw AxGkBCKUFMcOsLutQtgu95DAXD750h+61dcHQYimClOC +lgDi7rIIczd+q3Uq/Sf1pQ4ejnnC2sEfE9UZaVBdV3Y +-> ssh-ed25519 I2EdxQ oklgHl4X29HS5p4OiP9mTMP/x0KBwpcOzJH17DbyrHQ +yshidp3Mh8nNJXWe1XI2yawM5nVMzLJIG6RFjJJSoJk +-> ssh-ed25519 J/iReg Vf5iQoGuj/GVkZLgtURUfcTxigsX5UM5p/0M8O0IPhk +R/38k3vWWtmPTgDVQRkiD5fUISmAIB/nfiMM2GXy9Ko +-> ssh-ed25519 GNhSGw 3Nhd6Bg3hP+fSsMVSsBmiO54wL53idfudNfy5g0ochc +1thO78gP9E3uI7d6C+P32hbZOEu9jjQ2wzzvQncZGuo +-> ssh-ed25519 eXMAtA 2kRo2PhKljGOdJmxgopQieDuP8PebJAaCvVuQKME0B8 +W+DY+CA3bc9SweTPDKzpQfYaY0WQ5yXECQ0iEijmyfs +--- YgcrLEnv73CQulfZ13PN6nB2j9LY4k/HhKkAHnMITAo +X.Iѡ(b<@RX}'*͆k PcEYӢ@KT|ҮSiKE]RJ"вQ-MqAᗌG} /jOeOe]=֢TA^1%җ1=T0wQ k \ No newline at end of file diff --git a/secrets/apprentix/root.age b/secrets/apprentix/root.age index 24017e6..96909af 100644 --- a/secrets/apprentix/root.age +++ b/secrets/apprentix/root.age @@ -1,17 +1,17 @@ age-encryption.org/v1 --> ssh-ed25519 cZNEGg uRRHS8/vsv+HdtnzBv030V6t4t+D83BTvbo+urA6KEE -3vyBxkv2OvKW2yxZicgTn7DB/5qHwNOKgrRl+EaEYyc --> piv-p256 ewCc3w A9gVVQvR4Z/GQKf7CAE6Y19sLs6YO1BNrIls5Hy2QUnp -CKjuobW9dTvAQ8m6k29Ft5vlyf1ukqEhsrbwkZLcFlc --> piv-p256 6CL/Pw A2bbIwJfcbIlsfR5tJZopMXQaWrYXTl4z3JrMWzYWNuq -9zcXc9usX7F2qaEasnQZzS0dqNg6UqrB4krvCzMRhzk --> ssh-ed25519 I2EdxQ y4c0Z4xxhJMXH2Ej2AXNyfRdowqqn7xDpKdMSE8K1G0 -FIByr72GUA6XcPtNIy58DPsI7RCyFO1MZrQjqgbFEFk --> ssh-ed25519 J/iReg 0IUi0nvIQ0EP+kYxIEw2B5WgY9vP0BIIw+xE/j+XaTU -1ovtzwIE13/etKz1mMT2slbq6ZJ1JpSoOHeIBphlgbU --> ssh-ed25519 GNhSGw gefnPCvWFMMRUdL6yXOG580b/pQxzqpqJ/5nsSsIAmI -zjIjQiO/1wjteJmkmPKP62zDyZJkbaievkV//dXiEGU --> ssh-ed25519 eXMAtA uKvtz/zyt2UCWuuzI3sQerCwjzU2+4bNawRCzFA4YBY -ezDyL9NgmIZuyX06Suc64viWNXVEImIU4kH2AH3lD74 ---- 0m8XP8zS+MqhBXxgykk1n3TvAFtwxcQmHm8lzl+8Nd4 -{3' ssh-ed25519 cZNEGg U07qVHKKPTnNec45YacSj81WOdldljdjEQdL72NKs2g +MxNM0iHqUTJDBoE2Xl/ovLC7e4OL7upOntkrcDX+e7g +-> piv-p256 ewCc3w AlGn8NECtba8aqN+X5TmGC87nsegbLjK7UpHxUgaX83v +0xV4bnQ3TN1IP4cNWuLWltF+gCM8A/4v7ORxMogpIFA +-> piv-p256 6CL/Pw Al1LUjB+K/VY/E1sUoX4Qlnfatg1V66e+XVIsrSamcXr +MDqmazK6obyJ8biguYGK/LveQfzFOD04kanU0RoHERs +-> ssh-ed25519 I2EdxQ +atPfgkmMMTCVz9Wa3zD1a2sQ2dsmwD720ZB+7YT8TQ +TQzVlomVvpKwfXIdcfrzXRPuvCgF/xjWs8fFft/9ncc +-> ssh-ed25519 J/iReg mMizpe/Q6khZR4O6jfHiTPVECLlqCM2WX4zQQYrbYEY +KZLp6ViNKUlnrohG50Hc5HbmzB/sXOCO7Df1qY9wtCk +-> ssh-ed25519 GNhSGw YEM5IO+eh1TC2Occ0qzmTJyEVjjuIxs/YxVdhnz3WDw +OP3QpaUtHI42sznboP9YKBApS9ndetGTDe7q8u5x8Vk +-> ssh-ed25519 eXMAtA S6QeXCsoRYJ3z/8pWwtcdSx+D+irxG3llllia65gqG8 +RR29679nfP8NFbgEHQW+nnOuO5FWBscbxkwJnucE+hY +--- Qxn89y52+FWry4QBlq8DyuVOOg0l1/TaV5y7Baezu5I +Ua8ҦY`o>ְUǹ{qDnG ^NÀS \ No newline at end of file diff --git a/secrets/common/root.age b/secrets/common/root.age index b518bc750cd0ca041c0b3701590c467a3bb9e9d9..fb9f97588d0b4d6ba42f01bf994b7187413aca57 100644 GIT binary patch literal 1561 zcmZY8JIm_?7zc1ChYaq%IAn4Oueo1}AS7whq-k?)(lnDvlO}1>-0$EZ9Pk_HrsEmT z;3|lVjt3MK2M58`&)@}L&mdCg2c93#x&D zvP`ZS_S#c~G0vg$o`P2AS&C&-v?iy!4!LIqjWgi{jsVM6IbjY@1(=mUW8_%fob|M( zAX`D6=9^#QBZkgKl%&SJHh7E^kKhqL_vAziDN7?&BZ`5#?v;dIY^vUQ-1#QU<+k*s z^~yW}wkaAmVfTB|?lZSIo@HZhLPyvAbysdwj14_1)!h)Dk$Vl^RC6?fY}~7^w-r=@ zWZl>u0!_0axnPxjfKFbL1rh8uhA`T$40PI%t8oVv44cK>)ESUPndF6mDXRL28vjQ@oEE3d?X0V zFo_Ek23^k+rL@xW;&LLNCM2t;>#I=#>H90%-fMNV&qf18#Y4V!%bV?0(SE^Z86TDq z+3s+`G+JTKq9Io5&&}r;}GZjL$2TrF3FE*-_IgCnZ*BiA`|yAg+p(Gs}Op zDjgmxjHJM5~DD*ST%Tv!Obsjz?>wEBz$u>m(FI=0rt3bIm18%Vep3 zHP?TWEn`AB86a-ZA;+35M%;*SlR(V|D$-zLWSMVjqhCr=^u@th7(4?SS1Uz6as0rS z!b3__HM_Sa2;Te5t&GZg@c64f55$%IU$VDwYDO_g9!D}nSQbs_Q{nHnb}re{bg-Ie zbKK$!TdT?BXcwe2W8iQT+jP?`E5YXolOs0>5%}rF>Bj4bm>28FTNN7RC=1*7AF@#s zYcqnnzH|_Tud(ydN#;hFe55)M=(QfuKw9BvCJWYnOtzOb0aO-(i^yQ1l0>yDHgc&l zs%f$2I(76e^Ry(%j3zzR?@#wyw(-p@GoB)1#D8G1kc=6l#@|#b;{p`orQvD;)`17yt y{p#Z%3jWQnUt+)h{Qie;|MdAEAN>C8`MY1e_4+3-l4sJF-FMI5cwmRO;=cj>-T$2c literal 1451 zcmZY9xz6JR5C`xUr2t+4WVS+B!S~gyU2ECzXUo{n)>|t6)+EMYQ z1(|wLHZ84(=&gv-&{x>a5#z=#?{;Dr2#79w%2>(-Uyo&fL|962 zY-~(0`_>{>@LYrBZcP`!VBJ32mqG*Q5qKU0FS{t=Q#~Cc>*99-J6YQKz~H! zCNtqK=wy+!v?^EPYQm`_$jRFH$HTQ#$&QuioT;8`2CFIv3vl$&xIK6~UfR`c$unZs zx>re;dL||Fl;OM};@-pKTQ1lTa2VII;8w5Y9rE zKi6)lSuGBL8l|K}Y!zyvoju0B=9JmU+c480Wp~m7s+sc+JPO_w-aOjJ+I2%AuA`m;?LH;T{j}Hi9oz(~MYR>L?ap=az@&4)+!^3Bj4zS0R;|g3(RYDqBaV8Lh39sO>NohN-?0( z|16R>pxu*IfnlKEePW%aT)mHL0Mb*S&tt`ht`0xA(Jw-YRHeBYH@Dg_UBpdEQWN?V zp<>-NWCgXC#)mHDocO(S&XEpV;WzG4MaAU)G{p%|bAcTNPN4Lmcjv2OIFCHYC{dC3 z+yD}>iC=@7?V!5m;xV$r3XR1@yF##ygND(e0mB3j%pWUoIVe5;d}kU|x@iu? zrQ7xCY`T88W?Dq{b0`+4rSlpYHH(&4SUT&lg>|pN<~GuSK3sCJW+dqf>nR2fkboz< zi_h3|Fq-h;cHU{|3cis|whB*P0GuaCo63+_94&>K3%2hg7|H`)mD6UwA~BSMWkhHR z7jMkiKPRMt-aqyJx}$;J=ksrW zewlLmC!hZH(PzR}yKlZI*YVfiU%z$vK8j6?B|dNReR#2`7S5ohH-G;Git4UYe6hfP|t1&GkssV<5cR zZ>ye_)}&?zH({T|Qalz=O{>6rVz=fQE|H-TP@LA1e9VoZRWF8M5n&)99$d;fL+L-w zR=to7Be9^;C!|O}h@rfTr`$o5)$KyPg%)k6-QkKpnr%pmg4FSxYS~3DsVPJ_0JBu7 z%b5{gAx$lcDJ3ov&qD(3_c9WekRmQ1`*I}Z`!(u+R*cAl>=O%N8}X7@5YukDtw&)U zWQ{yls7LvHucJYP?mJZtX~vRdi43%@2HvS7y)rQn0V0}9iEzQh!yv%mrHIK0ZA-v5U4YQ zMw4K4T;>K9DV9$@K@69137L%&s?aGXXk8=h=5{R$swJfvggdV8GGL|7iF&*zT0R~h zIB2+D0UeCbQg#8O$!R}E(*_mjW(ruOnd!a5(3b3%DIZr z0K(jMElsz|>3*;eDHR|`$GT#o4dvoW3gv<1bdC3h;SAlh+(zF>$Yp()NlSrgl7jhs z2CvmRG?OmDs77T)2e5Q7sV6hlp;*;Sg+et=fVOK&t$?{$P_?F63+0V^OX3JoVf#dz zk^7RLt*LZ=81|@P*eWJ+Fr-rfI&MRt1D2r(O(seRmaS@vfckjeY_aJs$-$stf{kPt zLkfXVC=@J!gaG0#MI)-P1H;XBTJH`LEinapR?>!2phc;=BruXw3^YzeCJ!&)J8&`j zu61Yqt2dI(_3;}s^6|g^d1h&8^jEF%@_z2*`5z`89$Y_Fnk)T`9TAq#UD&m7Uzz*! zRebhE;`i5gUA;Dc<$Ypw;>>UGFULNAg}uD6e4Dj*O5Yi`R;EtOP{8#EBgcYY9*uvx zd*sv?k2|%|d6m2S2pFzVXOCaqC|*u&T*7W_uKw}8ZT)!o-qZ7A>grr|(zv52!qR(x zKYQrw7rwoIbKmTRnfAiT)K6RImQF98U5wTqWM;jGa|e6z&5xhDG&VdSZy4mw+gtlL zM_#IYbZs|#WUslfN2Pn?MddVf=CyCo5BAO{R*W4-)pwNf&hnKlN4)>s;`5z}Jr~#B e+WFa`$C&pCH@0ub)SDl^ot=7wt*zc&yY(+g_>T?% literal 1125 zcmZ9~-;3J>0KjpPJ-8m5vbo{8$<-Z$X{~9}CQWb0N!qk&(xkm6mo$x_v`Lz#X_K@` z+O#0M-A-?N5r0jl;)CLY8#0ld%6@RS;a;4A;LtgpcTC*rqv#xZJ5J9>?=Sekmv2Q= z!>TEF`nIcE=95-O1G-=!JP~x9dRSE;7zPtTS<0sgEyxh58l1AgVylV@rh_<5nBjFC zf)!s65quPlBNC#c^~#_ZfCOjrK@uEIH?kdQ_H z(`>yPwjme^s=bsP?FBK@>rDa?ty@uzxXa}`IfRkeXvf7h-Oq^)4)HQ+Ig{W7o(pi8 zt=3IYceAxdL?e{8Q_D`#7ER+Q({7eJ1sq`&ma{u4>wgxKqJhZ`%SU5k!3625Oh8f} z$8%_sV3LGN@#S0|Pem{Z^EA(kighiZvVe#nG8#>!6NbdcIxe75c^U!@uicdq&FPkU z&QuAL2xD_a5=tw6G1%!vrM8oD~ z$l(3RW{t?#IieTLF@PxwI!jlkvK*11V7h^-tV1-@#(-7(rXxdHqu|RW-U%SC=lgB5 z)2JIBQl5+$#Xi{rEtrHQ9*A&7LS)JUs47i2wpmj}26M3>L53Oz)+*tY(l;P(fKYU~ zYG(k6q#;D)<%*HjA7@$UaWRNQ zHhv6;gN`EgQyIPF#X&IOGh$apTB(Q?CO>MBkOAiPq}lu zp3vWU>h6WzW4C4&Czf{n{@LNZL+JP3gH&YqH|o|~gVkQUyZ*-P!@>7Qg@v!~e*V|UzUs-HkG^#6?-$1I3#>e_@8IIj%E+nMvAs8k jUnWl==xdAZ_VzzX`^6nwra@!o*cY8&Z~U_Dk+FXPCt#R& diff --git a/secrets/restic/apprentix/base-repo.age b/secrets/restic/apprentix/base-repo.age index 2343af3dbabd368101a217c94f27dce27f04ab33..5f45f13239b8a43ecd7bdfd1535d1c49807f30de 100644 GIT binary patch literal 979 zcmZ9|%j??&0Kjn%9yBLmh#*daGB2TPlIB$$zmYU)nlwq1_LsH^GRV70(>$A35%Hi- zM1BZPK^*8sMK3?bc2Ybz6nvnFqA(P^cvSS_MK8LezrWz~(r-tsfYix>$EJ~J5Q@ikjm;WP z00T*hGn;_OaXQfHD#@o}ShUc9l5}32^>q?=;iV#uJUE8$YFSwKG8893uNgUm2Bgzw zs1%85QadahlGH+gp~14gbfx&*!i7a zv1@xfW!P<1nPUWXQ|*5iF_QY~UCS96Dqm(rS9-^A<+NNCi%vjwkze5bLps{oW1;0I zslNp5!3S?$$NHe1^=UFNgV^>^eQTlZ zigW`sIPigURulW-+zY_{#^T09DuZEY1*6#lF{w4O6qB&YS~AIuB!DtW<)~EG0BE~f znhPl>N4(Q*#yq!k549G_YYMLtwUZ-r&3HCviwJdAVZ@^sLItW$kLJfr)?;0~9 zRUok)9P}yYGGwl7_nTdlX)*W4b7%QOv-)8cLQ=b3wC2@H#u&cgOwR}sndXU>_fU+H4HzD zY%;1@Wk7rLm>SV+Wy-??tVqy`m_SGt0KHxhRCuKy?~SN&Z~^Ut3|aoyzpH9&%4oH@ zik!gMOSY*~@2f|F$>F NzVNyA)5$9j{|hNAPY3`2 literal 979 zcmZY5%j?^8003|=B4&c@vRxDlBBHgw=KD?4rpO#=nxuKQO`0a@t$8$Qn^)5`Fa3cY zJPbU{jR}gdAu79y%!4r8MNz?vpfC|U>3oBNI4{1A&cEOTpQY!v+{jL=BwNSPQJi}1 z93ZWy8vDK9avg}{px>|;GR=C8L6hGs^sS`?FzyfrO-CF~ZNh*W%UeqFaUxn8ZfRCk zK_K~TI1stiw*5j07xNV(ZAR))*ozbyW^-^zcRjy2ID2vy|EneIyp=$_+i(lY#)<~n zXVe2M^OT7-TC-BjJxqf!l%0`1DVug}#!}$K0Ai!b3fZpuDyMQ$@XM;!vMXuDI~xyV7xgB#6Fj<1zExJ-$$tGx0LqpS=AzE<= zZ^4yvm^oe?PGgYpsgYM*$6bPbLhg53#x3ADVB0;aN#3ckvahm5qaQ>$Mf7SA0@>S| zO{r6loLQ`m7;89|uoB%ZtS~P9WLqF6UFo&lQ9JBxz#g2^NfPLAx{s|!WphVD+GQ{> zPYs;^-=u3uWJWgT11yd_h*>d1_3bK6%s9a@3{50>kTf^i;DIsbSk9giNZ}L=HSdI~ z;MWB^mqOEZ)&kImR+6MSkkNV;cQeTus2q72~Hd zZ|SwWcT7A$M^g|Z@4a^YtCwFgPkeXj^oK<2=*1f+quVDgocZO83(T!o!i&MxC$By7 z&`G=*U4Bfz`O#hH@45TR^>^-l?T_B`zpJ00yZ!JF zr{3)-B=x|XZ{K|4WBR$Lzx{@DUj6iRaqj-J@N87Sb$-`AK0cN%3eWgo-+1t+OE28_ Rc=GGBAN=^J@&0eG{|%BEQt3of5)nk*!b>*^5rQt|B|4e}K}b-)I&@h7f*0TK=j}$pT~M5xsp^ulc&coo z)fHUaeRtWm@m=6UC<+owC_^PVTAGm!6Fw+=B&vs@0>_7)a4;ai&>+SXC{`h=gmcz} zPM3=ldJuN4&2OPJ$aBkZy*$A^N3TuVYoNni2x6>Jakts?w_2k6n}A#*U>th{wq>&k4_kK{cT2z@>Ir@fjJm4JMEjC`vuHT7#3_jQFzbhvdMZTV-)!xh;w) zR?;!se1n>jE*kZojwwKKF6%urv?C0(W$k3ATkY!LW;84%{HPau+Vana-V`{>8!yHs_rb5)(*)b>r4U}@qt#%-J4kVYJJ^av{W= z#0q?<$zT{yi=NmiM&jigevh1^1o1<-u2|nR$3bN#leOh3xN>1x>(Q-N2VyoTOU|&{ ziciiL*VR-x5AhQ>WdL|_H^VL*3NOaR8BX*Jh3N$`DQo)ZoL3Ig1=MNuPZ<(}fk+7l zcotTrkpo-9cTb@8zfV@l^{#?YY#9vH86KA8%V4y0 z#a=A}su;1AkOiZ|X^QkXwhMa1>nT8+tnL1OgqdsVEX3jiwv(6xw6=`@UiN1YWqhAX-wAx(a(? z%US?dHa=SLwlO!BrYW#W*Sv~?SETAGB1!oLRnAtb)tMeEoT>D9T^ad)%lelP^-G#$ zX|HdPGXe-HeXFrjtg*uq5Yd#Fsr$~*F5b@V;MG88K*wtjVrqOj$rV=l=)fQIY@xYl zTn<2(+ncCvV@1-3|b-@hr`c+-+ll2 zA9Z{4FaP}8`h#cQ8nc(Y*Z;VF^;f6E(+@oS!B2nx(WhTLM?CuMxBl?Nlka}`{JeSa zp>MwU^FROgGynY;e&A)#!mD@o-PeAT;IDoB5%S58U;Fx(Z#-TbL)`Q6J7P|6TpwFQ0zsKT%d4q5uE@ literal 1525 zcmZY9&Fk9)0LF1;s8BpjolHf7cq>2i(j;vhIMSqfYnzwmz2Kou(!8~Ke`{1w+}Rft zw#yEJJ1N3WI@w7OMQ{htiXf;L+3=w5AnH8&^Dp@D<$-78dnaDv6vwOz)8x6d@WBQi zoV);(WpLuT2!_D~fW(+6`G8{kdDBq3DSo4lFN@x$7YE9)p0l8O$HMV`Q+$kp4e1ST!DzJ6B~|)~$K!pRX5F6>!2)QRS4+7 zwo?`#2YX&7p{7MaouTx>*caqRA|zI*^lq`=d)cPMW{#Rd$#^3=LdeiKo+TQl#HY0d z$#XCX%ta_Nk1pRc$8IBMt4Rg*<_1p?mshk%%XAv4x|Qx*ia+pC++lZS;WfKR z)>}t%!T{VCiz5}QyG`C%jR+8?r0&oqSEnnh$`pvKee1v&LW8cxy-fbwmW9nphG010 zwUjez0kRk#B8$nGXju3Sz4b~vhO>H-2WkY{E=|haNC|=st#$c8kEs5@A#StgUBu=> zf`V3U85ujBPSGIUBPKU(7TL~d|7+pY0+jDrombQ(g%uq{@T|Ztl zh|_5NICmI%6Aw&|l*?#WP+G2RDGfcU4Z7GfyfhVtplW%$-H;3+2VX1p>CJd#=5`8T z35lx|eWlf&&oe0!TeBh~Rny4rx-t>EiwcNlP2GSe1E1h0YKua++e?+C&Iu7tcbK8= z#sQ&88$>?tnn1KiSfroU1Tb8AK0td=%XS-Uye^M)p)JSbaA+O6n3mE2LzshD4VMAI zZ7~HMpkVxY1u0F`sRnXFdq zktZb;#LCo)_D&j2XhuUoFIXY zlsMQWoN{#NDnHMdS&S(K!VkVY^A_{;#T&(q_s-D2ZoYZmdOiK^#v6D4{&rrR1Ae^x z;xiYoK5^~#m7m{qC)rEkpWnZK;Zfk(uO2x6$475ni!K{)KNbD(&L^+F_V`WZ{Fj69 S`08!%VFiyb&tCa!dg~vH+W;H@ diff --git a/secrets/restic/jitsi/base-password.age b/secrets/restic/jitsi/base-password.age index ae8ba8aff1c5c49d52d2310fd387df536a73cba8..16622f46dc96fc1a5a18973d44564bc8ff7e9e21 100644 GIT binary patch literal 1125 zcmZ9}?TZw30KoCAGh|K5?u}iY3|B9TWt*MddDsoRn0=VpnSGjln3>%P!=2gRJnYWX zKFvN*X%hFUH1grB5V8m3)E8%UNKpjR)tiFUN-{bqgg7xJ5iP5;>t**BeBk@7X&?Y> zr9E`JhGReOv^Cg6e1U1G)6oN<9>Opr3K``(0Z2$2+Eq?tM@4~&G!Y}k@F@}Na2Y?9 z9n?H8T8s;c^jIwEtOnJOgv9)<9spOh08={%BIb5K1Le zM5o@$w`#*q!E73BI4!w}R4&+)eJ}?1b#H_haagfwN~*$a5hduZtd>e#m6SO{)HJ7* zZwt)YicBb39~2+~1PNfW7Ud>@q_mo~P$F6hiw&z$%oSN%<+{PXCzg8AWZzdwQw2pr z+7cekL_l8<)jCpTn8KhFjpZyI=t_+Td^MTyJ8Lx-Hi|%lY`2{r#P)`Xs00PyczDtG%D!J3|8N=DrHqMLolh#$gFrM$d*x6j1AKMoK=jqH3jRa zL2|7|=kyZYhkANXjL=c4$ly-dV*Qjp2zn-#8YTrGRAo8U4VrGZ8H-mGUv7Yv+y)01 zYN3|Lh6jpxNHr*TLqcjXylH{o*};Hdd}ab!YL|IsbzA{VRtT z-dudXJ^5U0{>x|LFMajw!jb!rZ@V=+_v8AH#-Iu5*Qu>%Z`_)>`N`gEhmO9xVf&#o z6YosicWN2xo|t<2KICz33Bk z_`CVLUp+zo_S$}?znhgOVc!M)1*z( zJQ}8m3hG30r~5%1V~#nU+z&d%Q{nD(Cmv1?K@fzwp*ZJ;ird7;{orH%==}vB`0|xh zC88LzHS7=CJ>%}4rGjoO6xkED?N&r-KrkFjh1+~1p`^o@FnREN1z`3y7>d}H%R6`=~f`R73=~Oh^M!SAK!GdhbBYQvswcre_*=3lo;aJng z|I_+yH`0e-B&>Lt9QVTMj%LE0YCuAIiXf~ZUMK`LJZp<&Kiw#&3jI=6094-8TJ&_t zjH3a-G7uE4T5MEnxU^z05}Rj)nCxhtY!wO!$d%0`N&U}4Fd8gevx2PeT83mBK4F@K z!5gtmnnN5Nk4G!*OrnuN-J%!hhC?}_xL$S?tAqt$G};+NS;go=qKo@g6d;;VD(^x_ zgFvB{?|2(4QV`zoxiIf|4i>GrW+M;yjI9rts@ODDpiF6XxHk>!v8IPqyJEG9WXxjam+D7Zo+^mlU!nW~vDYp=Ck$ z6E;Y)K~Yc%s2mz9uFBIy3FCN+>v99ZZ$%xmBw(CXD)q#HF-!;u zkwfya-bRgJTP4B^6S~aTB$I{H0n>Gac6?wt*+!$7g`>JrsmHrONfVQ*kwTde6$j&j z7IUWyH7izSJH0H$P^F+r*UW6z%HvWxS*M9=l1OPAHKod=2k9^%s+KiPYdU9l`AWY? zX)@oG@SH^UkbEaTG+BbH#K}r6=!H@(N=h~`AOIv!z*2GnmtvsO#(66fbjo(aQwF(W zlFoINYd=OJVLX!wSh3!8Vj2Q*exL2cIA)mi` z|H<}a>r+qezHjm354Vd4X6mCKeRXQ-?^{0I2cOKn{-gdN_1Yr2G`Iil6L*Y0gztHC zeR=8h!ENWx-Q+x%N_mrw#%E!F#PB0gnBN1!==PegF{=l9N0bQhVJ?ok=mJJ diff --git a/secrets/restic/jitsi/base-repo.age b/secrets/restic/jitsi/base-repo.age index b8e765a447ae6148b3f811006e77df72baa2074c..29d428d98459940af5e2474b0ac8fdeff0f517ba 100644 GIT binary patch literal 971 zcmZY5z02ct0KjoYPcR}1z70AAMF&GK&1;hrPLniEnm%ds)~1cOH0iH-`L$`2=3RIY z2mb&O2jK=0L2z&pg%iZ(4#ZUl7Y7k<5J4Qo8w4LmAOC_6-|spEJ&*=@do1EZdi9Wp zcts$+>*(ne^}vQ?7=lM*XEOi-I`uro3brM5=6V2TA-7G|uvl#-Nz|2UB~cN(KI0W( zn5>Z5V5u|h0fuxmP#KRtf+F6VP0->>-;|}@yzOEJZ!2HY|J9Ch)jN_5i-KATs2UXx zdlHNQ2P^2Q2ho-lzV@3ks>WJH38WEB#(73a7%$>;WG>CiP2Zds<37}j($Iicc@`U| zQ@C{WYT*gEv^U4nx(xnfvC-|iN%YXFz!HrPS8Os5%Y zxDqRfmT%)K)%}61Z>E;d;tVm!YolQ&f)9iY&tR5pBH>h4^5u(TQrPIrdwjIk8)g1Im>D^IjfRBBF=pP zI8#2zq%4^gZ_%b=@cydLY%qXRnPl=O9Fs;?{69^ZMz(UXk~WMrDO>vjPMvmPn^Q2E z1Ro2x3%q!79IDi!h4WtM{7nsI`|wq<9!-C+hT8@4`QF47IJm8-A7+CpwN7Oq^PG7uZ%9Gy?1vqAJnYL0#`e zm{$H8raH~Z$ZO;WH~wI|;N=I@C#Ii2Dtvk8MtP6AmAv>Vyz;^?-(G#_{wMGLdT+=i zw?6*r8f(0D``Px#JGX9%Uwrc64-51C?_Pi7o!{Pj_u literal 971 zcmZXS&CA;a0Eb~9NOqdAz33@0P+vlqG-;Cd@TzI^oit6}CT$wT(R?;(nlztHA~;aI z%MN#!KNpjf5F4^{C?}e>v@TjxBD_olWS=n zz;z$!y#^M=rsugRj`v4ksJo;`f!k6odCP(!58Wm{xzh0=>_t&JXtSA@o1)eqh`uor zBHjikv|0L!K@M`u}?(o*YDHmBN9RRHBq5nM6rXsS{s+BQbT46E?< zu3PPWPZiU$OVGM=;2m}N4~v+{u=J!yRuoXiZ8VTh(YmIg ziNcaoy0pSoEAWC`aWN+@84_|-$$;?8MUwFpklkX+VWiuIg%bG|%(_+OY#oG~Ako~n zX)KR-b7L%V;Xf7sy_pDyr(OV7v1CH9&NBHbm2KBI(u}3q!*J9}6JAfmqNWyRx=;v4 z12AZmXE{~&lZ?(4v7>kcQViIJkU|z4J+0^T8Y$JWyGEXMe@rZseHSs*qg zIggnot^d!>GpAHZfhc3gBRhbV653%?NN-b9;0IXg$DyT>+dSobRGTI3dcgoz5)39G zlCQm;AEQkq^xFTQ#F&EwlV?j2qE z>g(5K?il~zlj~=~Zd`w#=j=`O^LO9+<5Gy<{rK9| z*~_dg=9<12U0Uj5?2=A8QVd+_ebqn{t#KDvINc;$}6{g9gvpa1NI_mW?Z Get!a&ZA$e3 diff --git a/secrets/restic/livre/base-password.age b/secrets/restic/livre/base-password.age index 7a1182993603e28b952d3bfc57e5e51d0c82816c..4e3ceeb1cedf7abd7b12d5179f982d7a9b15fd5b 100644 GIT binary patch literal 1125 zcmZY6{fiWJ0LO8S9_KF{v# z(<~&#iG)N)MMe;Xnjv~c!bt=XdWdyr!6p3?WMXDUMt6jjNAzGncKrn}-k*)HXMgyJ|7_Sv|bSAb6qV)3>ta6P;ou5+m<;ImpR-4VCj!O~bi-qX3R&Cm+VG7+x-ujZ6{ z0SCnipV6m_Sl9 zIA^u85hN~U5WhcH<EtEeb3?A%w-tcq3jehTIu6UXt%_!H29^vNya&Qs-ylLl zh{~74i3|?1_MmQ9NfwocEF#iSsw+yVu+ZYvGHW1RrsOesBwlx;rDR1;RS6|3E8j3tA>y>G$+WJO)wJuBSvwNSIU*&KjRX}F<%DC_ zg6rFO$xicqpTE-TSDGQzLAi|OSF$C4lD3HfCwS2sqVNb@kZylB6kL#n5Y_}7Icx=^ z>1JAP#q&{p5X*Vde9V+^18y;i66x@K!_7w5YlfNt9pEcAgUP-CI>I0#a058ZIRakN zT&sz>MAxYgLVP~0Iw3X3vQV2rWh;qgx@I*Y)TJ)frV2&^*v*V)s*X+9ZKXqei>NDnKdF9ILi9co*p4~=|t!=n)dGDyB z@4t9vF2A_<3nMb}l6qwP*2su7SM8j9bmg~A@n<}6(5^qW^c?hUZs1twI()-LbL##3Ng_ZFu{Zb!Y0xLobdk|Nh3By*&TdD!F{%uAg>z(B{Rj jmd@`y_WchJ@L!+uosB#>yL)WUtJ}TJ3*&KCnmG3l9-osx literal 1125 zcmZ9}?Tgz40LO7AKHREv?2v(SLY<;$*K3kCO*8RE+oWleHcis!zzwfSnlx!1+B8o^ zRK$m!a_)R!4xcuh&p5fs&P}HxZgNhzp@<9)++Iv4GMIudHk{mic%}CjeE9x;GqMzw z3}Fyh!>(yengcoJf}!YC*tV6ZBtkF@qG1KL8*DmEgQ_eP8=0I07T`9C12nDJ7S&KP zY%_o@%4yZSfGW9u1`p|!qsDx{JPLSD(j$1*>B78%lJTk~r=X6=isd0W=mSt-|D#!5 zH)=s}GAwynA?}6KaIaO*=Qy3Rq{VkkQoR3Z>8l4LWE)v=P1rUkUv!UmUrK4Do&6_=h!pL5|uvU|rrSvczZtJyzP5Yo0_ZY$&QUo9+j-*nC*O@<9Q`!9@Y2>F#MkEEn0|8q%Ax7gZ?`U4 zvrAuF%S-1X95OzRJbHf9kzGH|Z&+S5aFTOu{`4Q`#$p>C-KR-szyBA8I zZP_@nRk=2Q^!D-F%2O-PT)ywji)YZ~{^_NMhWBid{)G1)o|@eE`JQd{+K%dCWv!m6LZ*ym;RW$*?6vfg(uG6bw8sV@66q_JB4gsU0nW(<3C>eye>Xqox| diff --git a/secrets/restic/livre/base-repo.age b/secrets/restic/livre/base-repo.age index 3fe67cd..bdf8433 100644 --- a/secrets/restic/livre/base-repo.age +++ b/secrets/restic/livre/base-repo.age @@ -1,18 +1,18 @@ age-encryption.org/v1 --> ssh-ed25519 h5sWQA /nZh6IZdBtv3woGAynnhXZXtNfKDODEoYCpVmHHhmyg -/UN8RBQr+0Wu5rPzFosqsmmiAAj8Etqx4eyzhLzcQ5g --> piv-p256 ewCc3w A8r4CYBN30fKpvBBaWomaepl0fxZwpUajIh0+BAmwjko -P/qLiF22BlrNlkT2EsxXCBBh6al8OaTtL6IVHHmeemU --> piv-p256 6CL/Pw A9H4fgwM3EmBo50xdWhNuWOnJjFFMigtrBQ4a0Rvx8N4 -jes6k2hlyuX5Ech5qTiChseaVI3YIzXG3p3UpWHylB8 --> ssh-ed25519 I2EdxQ LPJmQ47OOrMu88w16HV3GsJTpTcoG1Ug27BRCMrWSCw -OLgC8l7K9qOV5QA27uzPFIgMd+Ho2L/WyIUYCZEam4w --> ssh-ed25519 J/iReg 911LIrz86R1AWVKra7YkM3YwWKSOgkNHtFAgDNRYWB0 -J6DWXqaDVN3Z1LVo7LgqpjjBVjR2L2P1TZULu1FMeo8 --> ssh-ed25519 GNhSGw MQk3YSEF9jJjgH0M49LhlJzvcqdbBU2aqVpnxeJeZ2A -c6xqYIkdFIAx/wqAEeIoR3GrHsmReSDbzt9jtRPKsMs --> ssh-ed25519 eXMAtA KvWo6kdZcf3pjDZXTPXhHsGPrzE+wVai15ZT7tjia1U -be3K3ljtE1l/AAQ79l6D50SRJvLmVsT5lfY901Zyej0 ---- zfk/PCQfQSmHRlvwG50i/sHtPD5MXBuFfIKmPH6sbiM -W1O2_ݲ# c\SX *Z}Y-Y'䷺3 " kcU;Vnřm(5ȓ - 0n%hZj9O;Ch6QcgoTfDc \ No newline at end of file +-> ssh-ed25519 h5sWQA NbaGQkPeGlO8b90dNDmpHMW3lqsQ+zZzJIeu3YdQtRo +9DRyHnmmfLzMHesxvsv0TgFrMy/43ZnTIaEQ0bCuEO0 +-> piv-p256 ewCc3w A5Eq87Zy3EGEA24bv64pUk9P5wfYly/VDW1BMc9KBmDg +aO5+NSB6MfIF1qZwMM7hb+PVq/PBvZEEwG41iiCBEVA +-> piv-p256 6CL/Pw A4bfEx5KUE6g3LcPkm4A0WvKezGVQChUk8sKm0vxo2Y/ +XT3w632xeRDeKdP0bA5ToJrhUOa+TXLwEuF//k5fDiM +-> ssh-ed25519 I2EdxQ 506BqDUsZRUcsVGupRrpbwxAlmG1J9cjuFWm/RzIWRc +vyYTdr4SKXwXjbUpYciRXQCmyPn6G7hIDQ68/7ylzvQ +-> ssh-ed25519 J/iReg OIr8a+mk00ZyGmtnkPLI3YHObWVheNmVUEtcR1BObnY +EghXVM0RafcV5oF6NxDOo8BovNw16+CJXPS7sUWVzEw +-> ssh-ed25519 GNhSGw Ydt9k0r0RMkATfMzGE0j2QMD/SLZYGfunnOHk96Ba3I +HNoF/cXQvNNcuUl8yjAGrxxYkK6xaQrg3jfF2y3XRyw +-> ssh-ed25519 eXMAtA sSrnqDDYCVxjOa5TLDvDTMTTdVD/Qs6NjEOXigOWJ1s +tclKlqUhHW+nrp2FO9vYnVTyRtUHgzYkB7DW44luPKg +--- vQBy0qjY4Lws3czUbNp8M7kIX9llAMiEIQKLL/+IwSc + v}_jyzP{V +||X5wRtcrb[U}\3]aX@x_&H[Ulmԥi;qZ.m_}tw!^dP)XŋҺ/׊J>^ \ No newline at end of file diff --git a/secrets/restic/mediakiwi/base-password.age b/secrets/restic/mediakiwi/base-password.age index 00a6901fddcf834703d0957172ea52582ae67a73..29976d0e7ce37ade0597ea303dac34f3d874383f 100644 GIT binary patch literal 1125 zcmZY5`-|HI0KoAdhTy8gK!zwXE2ncBYF1;9 z-c3auD&Davo+qA6cH$7(#0NJO+~mE*Oc^x`d?Z5~y2E z)I?|i!y=T{&A_Bp2Piu4V+&q1?Xd!oP?ZD|t|5hPT6aaZZ$P{*L`A64R1LRW4dbzY zwU+J#S_p>wD{iEWx_**yvI>=?io95`<#eSX28R@^W=n{a)S6MNoYFv|Hw2imFzjoa zg-Vgk!6-_h8W^^?mZ5~ktx_OU^8+4P= zLT-}|;&ddq_F@d7hkI$if)ztzT#iYc4p3|~N#=)##88^<_Cl$S;D*{%CyVB6O<^)V zAe{jOwNTP@ol5reID9-p7E6WkuNS zeGV{H(iaE>{Ee1{5O_ORKx_zNidZ>>CLk?l=B0F*5F`b~0gqvW8KY$SCiZ@7TpeEj zGk!U4P}n(fj|{vI)&mclIA&ctYKJx<9pHZtm{acH=+aG`lpvvi#J(k7oBD zY+Re(Q-9;_%ioP}&n?V6clwv5Yda40kDmIpyztWM=%ddcTRvZoUSR(E(VqOgf8(Dg zHv-#szuRA)-Z^n{mp5_WXnAL6-`woYw|#th<{{s}V)NYg->&aJzxlvhiuL8-iB|_A zTZ?;VF8un_na!hTUw`NOdG(V!4zC>3kq2(gj6?(Py?N%zBkKoCBgVz|kH0VqZQ1z9 z%g_99*F9)Xn}l!pVBHs0-=$s6tLFx^d#`-e8J`Mlm|lWG@2FW@cktP<7e@;7S0a=1 qD^sh`EuZc9dXYcS9UG%h>{;A2aEN%!JT4yI#!KTzE|L#lI{Px&Dn^>(w-AlQwLewn>|`P1+=VBn|43G)?+S+NNn9 zWh#g=H^yYc7q?B^z)lcPPCXy&!*D9xag5Useh{Z@+z^q;Idq%2s>F@XJ!I}cWFo^ouNJ638joVVS)Oic=T^R7nQw zm3lG3v%*YRDVlXVPS~NWnoY>nYyu7FzBEJODv&f_8;!&_B%ZW+5cGytijSuU4NNj= z)liI<2{@Y<(@;`zS%0dgW^p~F6ty_7hpS6dpMn8^IXYEbUGfXcy?^FmH`@EP4kzsmKqjn#SF`_I#S0K zAqtB;Z<2`!+Za@%HPP-h8ihPfrhR-0ffdA!_erTBDmmA-^)!#NT|iIp!z>Mm`wO(!`1MtNguX zRefpy+@!p;=feZz`nii=FI+$M(@JG>`n~vzG`g|(S|;EuPqk7l2r+v4qT zQwPRFZv}2FuJBWLo&gne+dC`&>^|0c_RyZmhh6}L2k(3TqrK&C4&MHJbo|?gXLp(7 z2Z1-6qZ4mT|9s@Pn^!(t$bLL~T0JuV_|f6r?-%vuzkhXpIRDzo%JQGbKRCKJxK({% jG(Yz4V(Hr6 ssh-ed25519 Q4BaLw sE36D+xmCV+/+9g931TX6WaAPW9DxMOwmi90OuocjHI -q2YgqncgDC7/V7nSZEi3lx6kvgDpJeZlZo05sN8v6NM --> piv-p256 ewCc3w A2lMDz4LJuNkQ8xlb70glZ/xtdm0r2JEU4vTQ36ogy/V -4zqYME53mxNfFhO+P2rH7OyN262U0Q/nrZWZJ40hc+I --> piv-p256 6CL/Pw A7E9lfpNRlzE06ZV5H7WoUyPXRyDqv9jY54VtdFQqQkB -PjvFsXYJQzIDPcDQQhfdd4byDEwxWsoN96tnQGkCMAs --> ssh-ed25519 I2EdxQ kJbICcrfjuzQpTCJWQV1NqviGTnhPHU8mzuRPSAX0zk -vKqpdOKeT9SDHnANVPCX9RsiGA1dLp4WuQshl3uYfAo --> ssh-ed25519 J/iReg s6ubCGFE5xEKU4gZyB0oqkjV6JkCVZKp7IyzfJMdRRk -MGbb2QdkpzMJ9KYu4nHD1QMeVbbmVBZ3Ye4Hd+rXcog --> ssh-ed25519 GNhSGw j2XeY4L1W4v6xy19wuqCZWQImw6FPXFvIHBMc3x2cwA -Wb/ibMUhYYDTQDVP50wM3gK5J6EfSJ+FNJ/1zspOYzU --> ssh-ed25519 eXMAtA NHdvoWubAs9/6kF1Sr+2+EyoDXcfGdKcKErkGOZPHGk -SGvZCyPWpBHxi/GHgBMOrM9LMPqcAwRzoSGI+Gna2qc ---- 6VydlzKjJtIaRnblQalhEwKwjBbPL4drY4cc8sSdfhY -'UL|E5-.ǿV8#jz. [àj -?eېz>gڐ]cfLri-آm揦0ς)z]lMIA'/:~4CT8 \ No newline at end of file +-> ssh-ed25519 vZ8Vgw 8OnU8KeFIuWJYydGRiZSRGcTwO77RI7koFrv5WA+azU +Q++62sbB/W46nyMrySXJuyZ1D1sU3ZPlTSaZZWx1Ae0 +-> piv-p256 ewCc3w AtzXGD+Nq0l0k7BZmO0E38vKlwXDSI8AxhEbjBcTI98M +sDi+z6CnH+iDnbSLM/kuupW4h11SCboypMPJBNWoq24 +-> piv-p256 6CL/Pw AqVYkIH5b0U6wJdEYYGMEfs2GXwjTeMfcoD2CguIV9sl +yoFY5UZKpQ1ciglR7AJ3+BhP9A3bqkC3DCK6srMh/Gk +-> ssh-ed25519 I2EdxQ EEzraTR/1NpktuUjissu9SiQgQw5aBuBX+0M+vW0Jho +DvRHsiZuCI4Fv6MPDivipet6xGmSzDfvtTklYA5WWxM +-> ssh-ed25519 J/iReg K9HUWr/obr+iPXrlN48sX/p/N4uuSDchv4+vd86DQzU +jB/1eMx2jhfL5pspE3uSsANWYQTgq/9eadl1pFo0iYk +-> ssh-ed25519 GNhSGw dc7Js+ZOQu69i2dMf+40igpT039YgQvGzQzKhf1UCm8 +nTC8iryCLCO/wcO1XskTzHiym4LULiMuvrfAne7/QDY +-> ssh-ed25519 eXMAtA JFEG+7BweZ1IO6ZcWGJOz1Mg3NBg2610hkP6mDYhvH4 +sk/e7ho9ZwnpPl+liA0u0ZAQ7dNh12T+CKGYIZsXMfY +--- PmkzVrr554DrsOVvMmOvEa+04iK51XEi3HwqeqEP1oc +]VG7$͋<{1BQ…gĿ&٤T^Bfoe& q:8͍Z&)LШ1Ct6 jx[ITSp;Fq?@U1z2{R ?(*n]ܞ$lX \ No newline at end of file diff --git a/secrets/restic/neo/base-password.age b/secrets/restic/neo/base-password.age index 15a51e346196b06bb16f1ae8ae3e33dd221c7b17..7710cdaf0b90903cd5598249805d41e3058de254 100644 GIT binary patch literal 1125 zcmZY7-;3J>0KjpP6SbnlUkn7bARB{Ho8N6byjz@Q5>9b;osz3Q+DTAyjigw7&_xte0S8n*&LJuwGBytZq7hCf-Dr=XDf?f| zF{XkJgkpZ(B~;k;M^UiCC~i#d)UXtUMRHgLw8jKwxp0K7)m8w5sQ3Vuu6P7-W^bh6c6js|5zB23Z*wJtlwgRVh^mtGFGvq zY_5%Ks#oF4Y+WieH3}e%yo2#(HpTg}ERV@r(~M_h-8u>5VMQRt3Tepw5MP39)afLe zrB1nLLcr#V3>4S;8GkpGV}-b$wBLEQU}X=i|#w z)7ErVDJ9J&LE|Jtmoxf6D&{+2u>&-KMj)f4;aDXR*{rc4Bdb^aWg80U6K_;=0>YI<3&~MzE7J7D8VkFW5kp(0rkfu?wT^G)k(1{0Z$A8V zU?ukJuJB#^cXHdm7|!imK9ziIkuaaLeq?5D?D=i9^y%@RmcuvqJg`1~`h<0%4jJRe z=Xc)uvSPk{|Md1NGnWs1cI2fStL>rx)vCh0w`*!mJoh2;%r^YGfV~Khh5LK|94+4Z z^~K!BTMDzs@&{iTd+qS}{>)Q{Vq4n$k?d^o?#1a#x7_x^@Zy;hKg_IMNM7xqo#wsK z``2Dy7#{uP@|E4E9ygw6@-}jNH=jXN_xMqF+k#J~q?)FF5$0p{MPX2Oc zVt%OgMt6M6qQ~9^$B{43F9Zwr^@W@3v+rzs@H^tG$!8b;dSY^Vvi?UhasI8uJ%qNB k*?rZXoPHyJ@AB_Ez6lp{Mt)7#O%etgVn_efkqiNdok$2LjSKK6Nb7`71$)#!1 zbPg29ft)CeLt$(pGQ`t$J|@Q@+=lmq3~}njos*&Lhs`IBF&{rD8_plSzu?37D{E>@ zwHj{dc)D%vvRw`I8dvjh8~3D0M=r<% zEQgshg3%WYMKb*^SFFj^Vx|Oj*n%)A&tf8jXCUf-7MdkN_Ldc|YLpk38U+FECyY8Y zs0T!mtFvTN(p5TF;;XaiL3&`Dj1dw0a1JfZwc3hJ*@x20)2jg6UL% z9;I#62)ZQ@L9`;52)3ql(@=u9`ZKmerP@Fr!LzDM1XVZKsHrt7<7J{2WhSgZlU>uL?LmnNlCvg* zqdLbiqLS)@j+yFWWw{I>xd__{LdirGPnoPRoK>BxxU@Djem-9=<;2*D@ ze{b{d&$o>g;RpA8{LIwOlb3_T#&35nZk3Ox%nSbUvFDJimrv((ms=W=MsJM zuk7`^A3hvTY?`vYpfTA$^};>s!LPCh_emG#U)#I5-&d!#=Qrl|rfBnRnKSv&T^TW*8tB;xeOXAwXzXc+e0RR91 diff --git a/secrets/restic/neo/base-repo.age b/secrets/restic/neo/base-repo.age index c17d4f116e0d38d359c0138d775824dae2150540..c264cd5db589ed8a3dea33fff048580b503ac498 100644 GIT binary patch literal 967 zcmZY5ORM8#0Dxh^jbRv zAj6jjr&BlthtYUUFat~~JJ1Z69>9K2t1;Ym8!n@(HpRh=!$D>mP$w4sdV#HXs7~|= z!laZI8ufN%ERx3RXN2JAA?KHb%Rc!gl%49zhqpGOgdhVz@gUnRaAGr`;)4SywPQA1iElRQc zf=A|?h&_r~$vA~kQyuB?wLSki+yLA;VPxF;Y+afS{?`&9YgJ8~d?=D=7KlF(ucw{|O1fiUZ`dC3F1y)Bx zflK|Y`LT0VQ?O_Sf`LqxhZC0=y;-Tt^9Hinddg<#C|vIynL1RknM5H7l49y0zD1=+ zxgo{*TsTZ4VUh39q zpUE~YFIzngd0C7nc@9QRn;s+H7Mc=42yG9cNO6Q{HfGyhY=qR-!y!w3?+|0=YdQGwHY&W7^Tq!erZ+ z=PV7zVV?l0Nx4lEj&X*u@N-U$3K|&>hl9j63SV?&6}Wva`Yfp{JK95@`|OoJ9XWa7i?`l<><8-^0m7%gHMP6UH{i7kgZp0n{jclt!jb>x397A`bWu^@bnK+A55eG literal 967 zcmZ9|Nz2>>0DxhTN+F&^qz6Stq?HnFI>^#1S?NOu80g>V zRn@NVxd@KKR1X^EN3ZD}0TYLH~f&dFp=;D>E>jDd2u zP`Yp(#YtimpihT#vf`5xV<3G4byAIrX#!$FwoCZ47bS#uTeU}5VgtdFwcjgXCP}LV z-8(vxEJ_&W3e5F7gPhx@uQVQdRL<dPM@OU|y1&4G- zES$5N3Sr~IBJd6(`4TI8>P+1H}54>SZR!FQd z1}p8!Dx`)Sh_<-NFpOtI=>&43c1iP~aaJ46cKWF45rb)I70zUodTUB-h&AuQ?RXe6 zzF%97fmJBDBqL%t3>*OFu!*@0QV3IrV=`YwslFjP4VQE3C7fE}JJcCH}_Rx)|TXE3<{eHhEY*|E+>A|43Hp&=L+8Qe66k#u|b$zH}(l$9x zK7gPm4uJf__M2NTJs3NWpW=eq*BAP?t@oeD&tLrH?>B#r=ij~Y@>TBHx8#Sy`MKEBj!KYRO9{oB*GKKT6?`SZJ9T)+P4kCU4tadqw0?;k$5fBn(F3=vA= diff --git a/secrets/restic/redite/base-password.age b/secrets/restic/redite/base-password.age index b83837c..d6a1189 100644 --- a/secrets/restic/redite/base-password.age +++ b/secrets/restic/redite/base-password.age @@ -1,17 +1,19 @@ age-encryption.org/v1 --> ssh-ed25519 hTlmJA tFspJRtTzOyza8z4Zzr+eRRpr1FRgPLvVt8aa6DqQDs -h5QHohNRWwbRNYUo33IE9xgL+LVPdpqVi7KGo0WYJVk --> piv-p256 ewCc3w AqtGkKIz0RqFSpSM5Jyo/3ahGleDPFbFfoQ/oeO1F6h+ -oOhKcw5aHG0KjQT1ahzF56pSqUJMkc7g0GlyvVpHChk --> piv-p256 6CL/Pw A5/Z1NWvKwXQXRRsS5+F/HMYiCaxLYvNdeoWxE+mZebJ -2fLfML9Oh1KYYSmfnTPZMuQTO7aiPOUPyjEKQ2KMFUs --> ssh-ed25519 I2EdxQ HxBMMQL1+lgK9SHR4Ysi6vYP3EQ4mLvfERp6jzbA22o -aRAx8GUgxIzfMtNeGRYlUCJVHWCG3O4Y3pKllKa/kDo --> ssh-ed25519 J/iReg 6OY9ewuXbNqwpT0MGkN6wW+FjQrlRNP/yuWZTLWCHU0 -XAyI5iB/dAyTXld+jrtSeEGJPfkWz49DeW9l4XrCfig --> ssh-ed25519 GNhSGw YErp+AzaxxqONOhT7FXnIBqxnIlwDeTPlKkDuZnmoAo -JHrX1e2nZeZoRtL72w/46zBY4wMWOHeMYMZ/lherkXM --> ssh-ed25519 eXMAtA db2PQw9eEVjCjpJcYggQyAWYOrARJfCWyG6ZaL5/dF8 -DljpTyD1ynzHjj0Zxng3fCZm1nB3m4PLA02so6cbpy8 ---- 8KTgqVxhKRjYM3373b86GntdwjxteSfSKOSCRw8x8Mk -XZtG}9F$n!p }$]A08֞ErlZR@7O65":5+?X6d魚L=t|ΨMO2g?Yǡ2z ޒI͕[F՞">CzGʄv_ʛ oPY˨14H_+g|T^k1\!1/mpe ssh-ed25519 hTlmJA GGYgxZ9fef61srcfQRKzWlJxjZvM/aNXbj+VKQKqlh8 +hyqXuysxi8cZw7gkL6mzBT46BW7BdTbAq49SbikB6sM +-> piv-p256 ewCc3w AlPrqo8arxIDE7uKZYesXi8v/IScOoctlzSEMaV9SvPS +e98Dsw1FeTY0cQ399Ld2YWM9wt8dRmk4oAkQOal5MLk +-> piv-p256 6CL/Pw A/lTgaxoZsVzFKIWIwoIIpN+jbLcI02ccmpQkloLNj6R +pg9250zjo2S6sZ2+cROojICPpx5dwm6f5GUOaK0sstw +-> ssh-ed25519 I2EdxQ 3u/qjOhvHfxPqyKrnnUXLUy3LjDkHKy+zBQ43LFfDQU +V7dL40BcwNDZLxhkAEgsqZ47taPR0OKIiOo1t3NdVjM +-> ssh-ed25519 J/iReg UT3kqX2XtQUG03DwzIz9JzYop4thDZueEpn98cw/dwY +YlI6tiZ6IjcpPWv9PTY7t0QZEBbBxD3EhfiWboNr4fY +-> ssh-ed25519 GNhSGw sBz4SNHOdt3aZSQXGRfcIc9fo/+ExOcTv5DT9jQGBwU +ZbEg7kdpcT8LfO1NC6CaaVlxAsChq+U+c4p/QW2Tf+U +-> ssh-ed25519 eXMAtA SHIrXyEKX1jaf7TgrWKRVFkiNUas5soelT5OJfEZj00 +p9fB+4LLvuP3uqqCq3bjAOS6kS1aUaEzi6O00cqWbZo +--- 79Z0v+RfvqMnF89CMuesb71b+rC+zzZmMfJpAjaWpQ0 +KH.Ie Hl C zQfE31R-~q-^t}Jzg{llaF-vKTz-o> z1qG>49BWZ1g<7pkw>L-~oD_@Ip^Jk{9SVwqiy$g$o%H<$AHF{ptDqO;UbQPXagkpr zsujHL1HG5Qx{i8*kK%Zr1|u`gBnA{w-fqbu-JG-Hw$g{Po{e>-T(7lmM`u8|V0P6!sM)OFaaf%$=B3E2 zLM~nJB-J8PIBh8xl~_ir|7Q^+1)e^#T;B6zFInsJJLeO zIKg(spaODxFXwc|TQDrp$Z+Y5*CoGU1Bnalu#kMST3E-1lp_jqwnY(T{cZ;8Tt^p) zN68MF)CFmX%+~PoXmgS;!hiA_SXge-#&FS=0lL^ub%B?(+;?W4*g(=a8X$BbXdFi) zgP~;kO}IuHgu%{g5*+Kn3Z%8NMucf@t>|KIh#kMQs=3r@xH$>L2&Wjy?nHFVih>Fd z^8RF5Z-->zGJ*_`9e*N^x&+goL7P)-&!k5>w*B^VH zd;ACNnc&{xjd$Pfm$%>#uHJ~AdIkOQ_Wtg7KYcjdUSIuk`2ANOAG~?_jVmv{b9Zp= M!Jps@e*y3R4X2Py7ytkO literal 973 zcmZ9J&CA<#0LMMZke-|eF9RvNNvwHkk|r1Bm$Z3%@}8z`dYNtB+q^z$o22MThCD|P zPTWC3#!wjQPCHByr-)7v^rC_%6*mt~1bG-J;xkYB`~{!y=kr~Je$UU`!?xJOdG>UE z7{WWW(|e{{RZ-9PFoHnoZZu9(33P*+HxMD(QYK>?rEDOQ>sr}@JS<%gaTrsC=0k-@ zrc&f|bVpKbqULNb-(nSpqPiBO)L~)>EQ;?jSphNLPyJS@@CURa-t`KMAiI9ey8XHf z3VTw(xFgT&lG`mAk^)VROx=D{gqc2-##JWz!J!J$OzKcXGgNC6q(0E;flTu)#Cnxh zkaDy%X4rfrkB50{6`TbA$0Au7)=yYT&H|Z|XNb2PQs~^rC#fclb>l*feBY7!08t-INec1+eB3|UZYt8&!#E0o(WLREE7R}`_4*BISM=0J{J4NotqS&Kd`hjBqJ0ob*l)5KbZ|JFdwc-D{20ZoC>EKS<_%Xeic(}WOQpeJ*tDBwYRBSKv+S9% z$0W-NmdI(EKU2U4bkV4S@ktpiFuzfh$VNP}l+6JeWbCZZlg(Dk@l--Z+crA=2!=$9 zX2(&6YH=ZK@fB8XwpQSCtaA{oh!bcY8>Wkp5*|m4io4+hRd*j=8j_$BmA0^IS z`1&E}!uL17dE)gepMP=p-m~Xlehoc$ ssh-ed25519 qeMkwQ rHm8PBJzgu5JUR8LDwtfYKC1G9nNTp/RDhBwcE3gpX4 -bffg24UPtdd592oqZmRE5vBeLOawd06Ly1oQpPFWV54 --> piv-p256 ewCc3w AqnnaM/0jDUtsn5Pd2kOwNYccVX3qwh/fC9c2Xkn9oM6 -RZcY6yFcGTR5OkUFJ7NXHNZkfP2gHwixfR2jD8j6F7U --> piv-p256 6CL/Pw A/wYBpm+pSebrFJQaHAi6s160F6q66ZqNv4U0CPF/rqm -uyfoXqTdchDgdWLTtxiyr2GT9coVTcY0TodoPAXtOEY --> ssh-ed25519 I2EdxQ a0dWSVEUmhLbJ2kfXjvjDvRMtuCtRktm2m1pbTQpgzE -M3EgtQZw7Nyg/KfuD8R+Txji3PO/vQA2EtnXe25sHKw --> ssh-ed25519 J/iReg eoIguFRQZYm6dEUzz+BhblhT+e/auOZD8Sia/h6/yw8 -SFPkrWh++D7drO68mYpTSdYuarXtxuba4/ExYyjTcJw --> ssh-ed25519 GNhSGw +WPoU/HXAEyMS6IfdjhwX/GvoQMLxRyNKkpeN3tgu2U -gWP4dU+yBPsc01w4ORdjelYZGm8yQ9ad+4odR8zv7Cc --> ssh-ed25519 eXMAtA geKWTCA1dU7CyiQFpuILO0zuppQLpgL9ckTuj3f2IUc -3z/xc0qlMttG/bMY7C+W3cEB1AQCkunbTExhU0V4QFA ---- oBKJY7LxTDs/My0AHe1ctI8ZXl0ns/AoCdCpEJq3/aU -<:*oS[nl L2$77yB;a/roTT"}B -0>jDL~Ā5(a(UvRrqCԹ5Z(Xs=2ۏ"i(Ju{\W /Nݯ/{{igǛ7Fg[ Pv#)ɔzQu\ 47w3zcÝrRQ' -v6VUƣ1[3- \ No newline at end of file +-> ssh-ed25519 qeMkwQ 4Yv+meP8KxPmSL3BV5tMO6gH9vc3KZS7R8UxH4LN9Vc +fLe+2EkKQ+jS1p4qq/qFg+fmWd29Kzr2XQNYGflAnMs +-> piv-p256 ewCc3w Aqf/YPn9MI4ZIXnVpMvmILCcZ5mQV5mcgAOFVonorjYX +ciAxU/DwJ18AhRYS53Mk5WQuIks4MZu5SzA7N09hKZE +-> piv-p256 6CL/Pw A3W+GgXe/CXOV764Eo3k7v2XMCWLKgNO3oHIpNGgksSF +UrbC2mmhsFBphAovLpnOb3rd+rUXXiYcNoyeqZM3p8U +-> ssh-ed25519 I2EdxQ wZWrlRk60bb5yY/Q0VF8iO3aWJ+i2fJtJDWuKpXELWM +ivPpTiyfNTbNA7WArvdYq/oEHPiE9NK7bm+8Ibdafys +-> ssh-ed25519 J/iReg zwJ7KLm05O9W0mu88EOnvQkCPkagjTjHReMF2WjjJXM +LPWpF9wCxQsfbARLvrNPHTWNQFFFrCgozvdngJSqhW4 +-> ssh-ed25519 GNhSGw WtEN58U+/y87KULOnXbxpiDp/m9xx0FgPhMWbcevIho +cMhq8jYQvBUkZhP9MdrmJBOVCvKEXw5VQbd6ckWdNbM +-> ssh-ed25519 eXMAtA D/p3ss9/bly1YVUdE3uZG+Lt+2W93+8WCamP9epx0mU +hh92wX7vyhiBX/T+cCer9WYIPSM3ZKNRVKofVRmPQAM +--- JSvm35PTotoFJlSkXneQrENDdi3Usc9UbDDcJX8fCno +[djfNQ2TDS&PKIG{򚶩P\nawYV-~zEאܝ2[~=.J1b3PT?pʌ[BdPUkN +{kEO;ѱSB1s :}E]>dXҾ( iI +@T0L /%^{3P!jƃwFf051 [!ʆvxo1 +M4)gIyG8 %5z73p \ No newline at end of file diff --git a/secrets/restic/two/base-repo.age b/secrets/restic/two/base-repo.age index b45143f46d0ad140ab742aab87bf661e35b5a0a3..baa6214b75430d359a13731208371b8fab60a5a5 100644 GIT binary patch literal 967 zcmZ9|yX)h0003|oMS>!yoH}X5NovhIZMefTeI#j{q)D1IO$U#r-*4KylO}1Bjt)+O zAcsyOir`}qagpD0yBvrJ;-Dbhi6DA-$_qY!wx$V zNcTmwDE3_lNQNOeG>1x9>!`O@CuM66S3>8ID{@~y!D^BZoFQXreqH9!UDWM%iF5b_ z$+FdQo-bryX!@#fgj$y6P{p)N+62|CN)5Y+AS-r=Q2$TM{9`vG85V{0a7)!FUue}v ziYhsscUDuT?CBE7T%j&wuy70oF0&{hy&Pcm=!6VrF(6oMSPEpik(PxAa?mRxtYoRt z(M~3LB=1w+ozBbc|15SWW7@yg&NX&RNr{Tcwj+w2&!7h9+;nF8c07=XDNmnR%U2jr zG7xqj=qI<@GPxKS7~#(beG#s0-7!T83`tcM8#U;xRA23%U!0I*(C8>LP%Yr_v{r1WVDeanmC@0|QWtrY(CbN*gIWNvrIN5c%^O zTq%6XqX%-d6a=Lc^8FESI7N(;br9hy*uo|@t-WJlK0}5Ja$zC6HqyohZ}o^+D6Fh$ z7N(}1LbZ;zMuLyQeB+NMSw-b7jdZ(R6dMapme&H;99@C4oAqHWRCNDH4Yr9_0VdT2 zmY>eKzDFb1F0X$5&imoBZ^w^adgAx1&ecbR`+mDKedqfd+=U0`XCGeu>C1ciqaXaK ze*`Xl{LGI}zklUf^`%d~LEeLJ>KB{K$15l5p8KTn#;x1;-+KGTL%+CWBi;>OfA=l< x>)^HLp8{tuf0bXjcJ}$rPtSh-fuT^_rZT!5>=gqq8Sv_g5RxC zt^gRL(spC7Eko!gisdc`Z!-XEB0`>;GEBUN@X-M6*)u_avWJ(oJH}7~CfE4j=8zM8DW0 z#GVRm5?NZJq|VyeaS!l?;qKSsFjkHgzjj$m23R2Fi>Ma%5)^i47bnzc-OSNLPRtmw zXXv3Z+*f=GcckDCcn<0cPBlFu-H);`o*KMOAerlmMJh)OF)qVIQN1}nC;_H+DI1E# zGL3pkWTN=MwfoIB{wTZj7d%>Ou90h(OGRg4b5?l5_s#b;-+w!0C2$a zWS_7uj&UKg3bvk#V>ehQ3x(Ix;~asF+jV+ zuM0gjv{%d~Hrmocp1S7!b@Og>OZ(xC;w1aUcz@!(q(6K5?e$L|-23|7@9)-6TzZ9i zk9zIpkM93?{+-)j{rw4lr}O3?_rE#1aD3zF@{3PDd2}VZa&qs&Pj5f-@yWy8wJ!$e zp1XSP=FjQp+2fa;t7ZP^^v~b5UvHl$A9=rhdE@lX?u8Gy4?nwfeKcU6k8XYU)?@zw DQBzLm From 9324d477025d97736d42f2b9e66ab0eb8b208e3f Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sun, 25 May 2025 00:59:21 +0200 Subject: [PATCH 09/77] Ajout de srv-nat --- hosts/vm/mediakiwi/networking.nix | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/hosts/vm/mediakiwi/networking.nix b/hosts/vm/mediakiwi/networking.nix index d4accf4..178d245 100644 --- a/hosts/vm/mediakiwi/networking.nix +++ b/hosts/vm/mediakiwi/networking.nix @@ -24,6 +24,34 @@ }; }; + + ens19 = { + + ipv4 = { + addresses = [{ + address = "172.16.3.144"; + prefixLength = 24; + }]; + routes = [{ + address = "0.0.0.0"; + via = "172.16.3.99"; + prefixLength = 0; + }]; + }; + + ipv6 = { + addresses = [{ + address = "2a0c:700:3::ff:fe01:4403"; + prefixLength = 64; + }]; + routes = [{ + address = "::"; + via = "2a0c:700:3::ff:fe00:9903"; + prefixLength = 0; + }]; + }; + + }; }; }; } From 08dd672ecf76d6a280dd037f3abf8ed223013cef Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sun, 25 May 2025 01:26:18 +0200 Subject: [PATCH 10/77] Ajout de mediawiki --- .gitignore | 1 + hosts/vm/mediakiwi/default.nix | 2 +- modules/services/mediawiki.nix | 8 ++++++-- secrets.nix | 1 + secrets/mediakiwi/mediawiki-admin-passwd.age | 17 +++++++++++++++++ 5 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 secrets/mediakiwi/mediawiki-admin-passwd.age diff --git a/.gitignore b/.gitignore index b2be92b..87a3018 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ result +*.qcow2 diff --git a/hosts/vm/mediakiwi/default.nix b/hosts/vm/mediakiwi/default.nix index 09e1ac4..da91291 100644 --- a/hosts/vm/mediakiwi/default.nix +++ b/hosts/vm/mediakiwi/default.nix @@ -6,7 +6,7 @@ ./networking.nix ../../../modules - # ../../../modules/services/mediawiki.nix + ../../../modules/services/mediawiki.nix ]; networking.hostName = "mediakiwi"; diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index bfdca9b..b3cb51a 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -1,4 +1,4 @@ -{ pkgs, ... }: +{ pkgs, config, ... }: { services.mediawiki = { @@ -16,7 +16,11 @@ ]; }; - passwordFile = pkgs.writeText "password" "cardbotine"; + age.secrets.mediawiki-admin-passwd = { + file = ../../secrets/mediakiwi/mediawiki-admin-passwd.age; + }; + + passwordFile = config.age.secrets.mediawiki-admin-passwd.path; extraConfig = '' # contact info diff --git a/secrets.nix b/secrets.nix index 09292e5..ac579c1 100644 --- a/secrets.nix +++ b/secrets.nix @@ -111,4 +111,5 @@ in "secrets/apprentix/root.age".publicKeys = [ apprentix ]; "secrets/neo/database-extra-config.age".publicKeys = [ neo ]; "secrets/neo/matrix-appservice-irc-password.age".publicKeys = [ neo ]; + "secrets/mediakiwi/mediawiki-admin-passwd.age".publicKeys = [ mediakiwi ]; } diff --git a/secrets/mediakiwi/mediawiki-admin-passwd.age b/secrets/mediakiwi/mediawiki-admin-passwd.age new file mode 100644 index 0000000..998f40d --- /dev/null +++ b/secrets/mediakiwi/mediawiki-admin-passwd.age @@ -0,0 +1,17 @@ +age-encryption.org/v1 +-> ssh-ed25519 vZ8Vgw oEYPtmVUnoOjjiYyB/k1K70aTaUc8yaFKgnftR/vRls +20sp0o5Bi8ebZVxltOu8JAFS+7pRLZnkvI7Z46BBmtU +-> piv-p256 ewCc3w A/amtNzHFy9pfbpYyu/PHCt0Cr5BGAUpyVb2ll3nX5VG +wPsC1ZZOyIOhWJmT7mKrp68RlB6RgfObULJ3kAXA+f0 +-> piv-p256 6CL/Pw AiURYPR5gbbz8EW8FbcSZPL7qfLeUr6MReSFPZYRR6k6 +IOfrVsSGWmvDqW0ur8Rb0G9q98Qbc7Q9Hcw+J7twKeA +-> ssh-ed25519 I2EdxQ cw4/OfxXByu8afEzMGNDji9TkZ7SpqjG3tFsgMw09T0 +b0qOsH70/DxhOQ2jpyeYliIql707+PacbZykqXkjCaI +-> ssh-ed25519 J/iReg 63ftKWZoU2/CI5338/bN99Npw5rNCRtptcUdWGSVTgY +AokJ3oTUGe9BHstt6MnwQZk3w9ONIVfkist1ZPlBGXc +-> ssh-ed25519 GNhSGw rcUHAE36kMf2pv2dNP6oVwB8Xz1qMLANiRSNUG0bQAk +5KhEzAAgDEwDor3xtLMuF7CSfBWCfoykROVabjpqQIc +-> ssh-ed25519 eXMAtA 0j0xZb7QWe1YqxHKncTyoMiDR120F8k+DvvNmedeti8 +kf1DjOIO3oJrGs+0L5OmFXb0Or0HAOuEjUci3sHIidI +--- ZxETwXAvw7vFD19/JKxp9kuIcODkoXWJW3HDKZZ93IU +d~Ss@1q-?$Ug Z'xbb?vh=s \ No newline at end of file From 43dad73fbb06f2c95f9369c52288472536db0d74 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sun, 25 May 2025 01:30:25 +0200 Subject: [PATCH 11/77] Erreur de config --- modules/services/mediawiki.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index b3cb51a..7ac3afd 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -1,6 +1,10 @@ { pkgs, config, ... }: { + age.secrets.mediawiki-admin-passwd = { + file = ../../secrets/mediakiwi/mediawiki-admin-passwd.age; + }; + services.mediawiki = { enable = true; @@ -16,10 +20,6 @@ ]; }; - age.secrets.mediawiki-admin-passwd = { - file = ../../secrets/mediakiwi/mediawiki-admin-passwd.age; - }; - passwordFile = config.age.secrets.mediawiki-admin-passwd.path; extraConfig = '' From a0a62c5d629b983127197c32871b8230bb15749a Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sun, 25 May 2025 01:36:49 +0200 Subject: [PATCH 12/77] =?UTF-8?q?Prendre=20la=20bonne=20source=20pour=20le?= =?UTF-8?q?=20th=C3=A8me?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/services/mediawiki.nix | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index 7ac3afd..05710ff 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -36,9 +36,11 @@ ''; skins = { - Citizen = pkgs.fetchzip { - url = "https://github.com/StarCitizenTools/mediawiki-skins-Citizen/archive/main.zip"; - sha256 = "sha256-6ZhrAHAKWHGij1qBgigEll0xbGhEOo0hYTMuakkU06o="; + Citizen = pkgs.fetchFromGitHub { + owner = "StarCitizenTools"; + repo = "mediawiki-skins-Citizen"; + tag = "v3.2.0"; + sha256 = "sha256-XtjYMBK2pINwbAzAKFJ+nWxcFXQd2E/Gqf3x+3Pa8VE="; }; }; }; From aae190c5ccab84ddd2cfcd920fbb31e4adfa82c6 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sun, 25 May 2025 01:43:49 +0200 Subject: [PATCH 13/77] Ajout du bon owner --- modules/services/mediawiki.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index 05710ff..3ceae2a 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -3,6 +3,7 @@ { age.secrets.mediawiki-admin-passwd = { file = ../../secrets/mediakiwi/mediawiki-admin-passwd.age; + owner = "mediawiki"; }; services.mediawiki = { From 035b6780ff4ce5bac26c52438d594fca579450d8 Mon Sep 17 00:00:00 2001 From: Lzebulon Date: Sun, 25 May 2025 02:56:38 +0200 Subject: [PATCH 14/77] fix nginx & add visual editor --- modules/services/mediawiki.nix | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index 3ceae2a..0353baa 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -17,10 +17,15 @@ { ip = "127.0.0.1"; port = 8080; + ssl = false; } ]; }; + nginx.hostName = "mediakiwi.crans.org"; + + webserver = "nginx"; + passwordFile = config.age.secrets.mediawiki-admin-passwd.path; extraConfig = '' @@ -44,5 +49,10 @@ sha256 = "sha256-XtjYMBK2pINwbAzAKFJ+nWxcFXQd2E/Gqf3x+3Pa8VE="; }; }; + + extensions = { + # activate visual editor + VisualEditor = null; + }; }; } From a1e70f857403beffe21d758d031dc9c65c29911c Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Mon, 26 May 2025 00:26:36 +0200 Subject: [PATCH 15/77] Ajout de quelques extensions --- modules/services/mediawiki.nix | 36 ++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index 0353baa..3475c61 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -9,18 +9,7 @@ services.mediawiki = { enable = true; - httpd.virtualHost = { - hostName = "mediakiwi.crans.org"; - adminAddr = "root@crans.org"; - - listen = [ - { - ip = "127.0.0.1"; - port = 8080; - ssl = false; - } - ]; - }; + name = "Wiki Crans"; nginx.hostName = "mediakiwi.crans.org"; @@ -29,6 +18,10 @@ passwordFile = config.age.secrets.mediawiki-admin-passwd.path; extraConfig = '' + # Server Settings + $wgFavicon = 'https://www.crans.org/images/favicon.ico'; + $wgLogo = 'https://www.crans.org/images/crans.svg'; + # contact info $wgEmergencyContact = 'contact@crans.org'; @@ -39,6 +32,9 @@ # Disable anonymous editing $wgGroupPermissions['*']['edit'] = false; + + # Extensions + $wgWikiEditorRealtimePreview = true; ''; skins = { @@ -51,8 +47,22 @@ }; extensions = { - # activate visual editor + # Enables embedded extensions + Math = null; + MultimediaViewer = null; + SyntaxHighlight_GeSHi = null; VisualEditor = null; + WikiEditor = null; + + # Popups + TextExtracts = null; + PageImages = null; + Popups = pkgs.fetchFromGitHub { + owner = "wikimedia"; + repo = "mediawiki-extensions-Popups"; + tag = "REL1_38"; + sha256 = "sha256-iguedL3pih3XnnAMt/kV+PVCu7MRCuKXXI8zGu9tjZw="; + }; }; }; } From bea0b48110d8d8053a506654b17e5c0c0ce5c463 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Mon, 26 May 2025 22:51:24 +0200 Subject: [PATCH 16/77] Fix de l'extension Popups --- modules/services/mediawiki.nix | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index 3475c61..2a8a39f 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -1,5 +1,10 @@ -{ pkgs, config, ... }: +{ lib, pkgs, config, ... }: +let + version = pkgs.mediawiki.version; + major = lib.versions.major version; + minor = lib.versions.minor version; +in { age.secrets.mediawiki-admin-passwd = { file = ../../secrets/mediakiwi/mediawiki-admin-passwd.age; @@ -60,8 +65,10 @@ Popups = pkgs.fetchFromGitHub { owner = "wikimedia"; repo = "mediawiki-extensions-Popups"; - tag = "REL1_38"; - sha256 = "sha256-iguedL3pih3XnnAMt/kV+PVCu7MRCuKXXI8zGu9tjZw="; + rev = "REL" + major + "_" + minor; + # Le SHA doit être changé à chaque nouveau commit de traduction. + # Pas de meilleure solution à ma connaissance pour suivre les releases. + sha256 = "sha256-deKDEC87yTQrToZC5yNAH9tmV/5pFa4gsaSuOYXfIEo="; }; }; }; From d6b3f7791e5376b0abb5e98a1e8eea31730d7535 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Tue, 27 May 2025 23:24:25 +0200 Subject: [PATCH 17/77] Ajout de la conf mail --- hosts/vm/mediakiwi/default.nix | 1 + modules/crans/nullmailer.nix | 16 ++++++++++++++ modules/services/mediawiki.nix | 39 +++++++++++++++++++++++++++++++--- 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 modules/crans/nullmailer.nix diff --git a/hosts/vm/mediakiwi/default.nix b/hosts/vm/mediakiwi/default.nix index da91291..0dac7f5 100644 --- a/hosts/vm/mediakiwi/default.nix +++ b/hosts/vm/mediakiwi/default.nix @@ -6,6 +6,7 @@ ./networking.nix ../../../modules + ../../../modules/crans/nullmailer.nix ../../../modules/services/mediawiki.nix ]; diff --git a/modules/crans/nullmailer.nix b/modules/crans/nullmailer.nix new file mode 100644 index 0000000..23bb4ef --- /dev/null +++ b/modules/crans/nullmailer.nix @@ -0,0 +1,16 @@ +{ ... }: + +{ + services.nullmailer = { + enable = true; + + config = { + remotes = '' + smtp.adm.crans.org smtp + ''; + adminaddr = "root@crans.org"; + defaulthost = "crans.org"; + allmailfrom = "root@crans.org"; + }; + }; +} diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index 2a8a39f..ceaa59d 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -23,15 +23,42 @@ in passwordFile = config.age.secrets.mediawiki-admin-passwd.path; extraConfig = '' - # Server Settings + # Server settings $wgFavicon = 'https://www.crans.org/images/favicon.ico'; $wgLogo = 'https://www.crans.org/images/crans.svg'; - # contact info + # E-mail settings + $wgEnableEmail = true; + # $wgSMTP = [ + # 'host' => 'smtp.adm.crans.org', + # 'IDHost' => 'crans.org', + # 'localhost' => 'crans.org', + # 'port' => , + # 'auth' => false, + # # 'username' => , + # # 'password' => , + # ]; + $wgPasswordSender = 'root@crans.org'; $wgEmergencyContact = 'contact@crans.org'; + $wgNoReplyAddress = 'root@crans.org'; + $wgEnableUserEmail = true; + $wgEnableSpecialMute = true; + $wgAllowHTMLEmail = true; + + $wgEnotifUseRealName = false; + $wgEnotifFromEditor = false; + $wgEnotifRevealEditorAddress = false; + $wgEnotifUserTalk = true; + $wgEnotifMinorEdits = true; + $wgEnotifWatchlist = true; + # Peut-être utilisé pour les Wikistes + $wgUsersNotifiedOnAllChanges = []; - # default theme + # Theme $wgDefaultSkin = 'citizen'; + $wgCitizenThemeColor = '#AD1F1F'; + $wgCitizenEnableARFonts = true; + $wgCitizenEnableCJKFonts = true; $wgLanguageCode = 'fr'; @@ -40,6 +67,12 @@ in # Extensions $wgWikiEditorRealtimePreview = true; + + # Debug + # $wgShowExceptionDetails = true; + $wgDebugToolbar = true; + # $wgShowDebug = true; + # $wgDevelopmentWarnings = true; ''; skins = { From 7b5c12a52ef0fba2f4776cffb890b7665948e715 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Wed, 28 May 2025 00:27:59 +0200 Subject: [PATCH 18/77] =?UTF-8?q?Ajout=20des=20extensions=20d'Auth=20(enco?= =?UTF-8?q?re=20=C3=A0=20config)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/services/mediawiki.nix | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index ceaa59d..20b9b16 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -41,8 +41,8 @@ in $wgPasswordSender = 'root@crans.org'; $wgEmergencyContact = 'contact@crans.org'; $wgNoReplyAddress = 'root@crans.org'; - $wgEnableUserEmail = true; - $wgEnableSpecialMute = true; + $wgEnableUserEmail = false; + # $wgEnableSpecialMute = true; $wgAllowHTMLEmail = true; $wgEnotifUseRealName = false; @@ -103,6 +103,32 @@ in # Pas de meilleure solution à ma connaissance pour suivre les releases. sha256 = "sha256-deKDEC87yTQrToZC5yNAH9tmV/5pFa4gsaSuOYXfIEo="; }; + + # Auth + PluggableAuth = pkgs.fetchFromGitHub { + owner = "wikimedia"; + repo = "mediawiki-extensions-PluggableAuth"; + rev = "REL" + major + "_" + minor; + # Le SHA doit être changé à chaque nouveau commit de traduction. + # Pas de meilleure solution à ma connaissance pour suivre les releases. + sha256 = "sha256-3+nzeWemVAHGmLz3ZMvDSvP2UCmsnEiGJcE/oEakr2s="; + }; + LDAPAuthentication2 = pkgs.fetchFromGitHub { + owner = "wikimedia"; + repo = "mediawiki-extensions-LDAPAuthentication2"; + rev = "REL" + major + "_" + minor; + # Le SHA doit être changé à chaque nouveau commit de traduction. + # Pas de meilleure solution à ma connaissance pour suivre les releases. + sha256 = "sha256-oi5rliHb4KnLbvQxO7MGuLp/FEucoGR/Z0NP1gmbgMc="; + }; + OpenIDConnect = pkgs.fetchFromGitHub { + owner = "wikimedia"; + repo = "mediawiki-extensions-OpenIDConnect"; + rev = "REL" + major + "_" + minor; + # Le SHA doit être changé à chaque nouveau commit de traduction. + # Pas de meilleure solution à ma connaissance pour suivre les releases. + sha256 = "sha256-KoBULn53xnY+ydodeTGN7YEoqgLr9qhhuR5mNibbh5s="; + }; }; }; } From 2b76fad50c2e14b78cf2a375cf064eedda406d5b Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Thu, 29 May 2025 18:50:05 +0200 Subject: [PATCH 19/77] Ajout de l'Auth LDAP --- modules/services/mediawiki.nix | 37 ++++++++++++++++++++++++++- secrets.nix | 1 + secrets/mediakiwi/mediawiki-ldap.age | Bin 0 -> 1544 bytes 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 secrets/mediakiwi/mediawiki-ldap.age diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index 20b9b16..1f1f208 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -11,6 +11,11 @@ in owner = "mediawiki"; }; + age.secrets.mediawiki-ldap = { + file = ../../secrets/mediakiwi/mediawiki-ldap.age; + owner = "mediawiki"; + }; + services.mediawiki = { enable = true; @@ -53,6 +58,22 @@ in $wgEnotifWatchlist = true; # Peut-être utilisé pour les Wikistes $wgUsersNotifiedOnAllChanges = []; + + # Auth + $wgPluggableAuth_EnableLocalLogin = true; + $LDAPAuthentication2AllowLocalLogin = true; + $LDAPProviderDomainConfigs = "${config.age.secrets.mediawiki-ldap.path}"; + $wgPluggableAuth_Config = [ + "Compte Crans" => [ + 'plugin' => 'LDAPAuthentication2', + 'data' => [ + 'domain' => 'crans' + ] + ], + # "Note BDE" => [ + # 'plugin' => 'OpenIDConnect', + # ] + ]; # Theme $wgDefaultSkin = 'citizen'; @@ -77,6 +98,7 @@ in skins = { Citizen = pkgs.fetchFromGitHub { + name = "Citizen"; owner = "StarCitizenTools"; repo = "mediawiki-skins-Citizen"; tag = "v3.2.0"; @@ -96,16 +118,18 @@ in TextExtracts = null; PageImages = null; Popups = pkgs.fetchFromGitHub { + name = "Popups"; owner = "wikimedia"; repo = "mediawiki-extensions-Popups"; rev = "REL" + major + "_" + minor; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-deKDEC87yTQrToZC5yNAH9tmV/5pFa4gsaSuOYXfIEo="; + sha256 = "sha256-Vn/XGVYvM5doPtTEONESdVhgZlH/Fku74MeQbGrwU/E="; }; # Auth PluggableAuth = pkgs.fetchFromGitHub { + name = "PluggableAuth"; owner = "wikimedia"; repo = "mediawiki-extensions-PluggableAuth"; rev = "REL" + major + "_" + minor; @@ -113,7 +137,17 @@ in # Pas de meilleure solution à ma connaissance pour suivre les releases. sha256 = "sha256-3+nzeWemVAHGmLz3ZMvDSvP2UCmsnEiGJcE/oEakr2s="; }; + LDAPProvider = pkgs.fetchFromGitHub { + name = "LDAPProvider"; + owner = "wikimedia"; + repo = "mediawiki-extensions-LDAPProvider"; + rev = "REL" + major + "_" + minor; + # Le SHA doit être changé à chaque nouveau commit de traduction. + # Pas de meilleure solution à ma connaissance pour suivre les releases. + sha256 = "sha256-bpj+MS4XKLoDdtEK+Mv7Ifa6qz215jKhSL1DOOw4ZPs="; + }; LDAPAuthentication2 = pkgs.fetchFromGitHub { + name = "LDAPAuthentication2"; owner = "wikimedia"; repo = "mediawiki-extensions-LDAPAuthentication2"; rev = "REL" + major + "_" + minor; @@ -122,6 +156,7 @@ in sha256 = "sha256-oi5rliHb4KnLbvQxO7MGuLp/FEucoGR/Z0NP1gmbgMc="; }; OpenIDConnect = pkgs.fetchFromGitHub { + name = "OpenIDConnect"; owner = "wikimedia"; repo = "mediawiki-extensions-OpenIDConnect"; rev = "REL" + major + "_" + minor; diff --git a/secrets.nix b/secrets.nix index ac579c1..332b722 100644 --- a/secrets.nix +++ b/secrets.nix @@ -112,4 +112,5 @@ in "secrets/neo/database-extra-config.age".publicKeys = [ neo ]; "secrets/neo/matrix-appservice-irc-password.age".publicKeys = [ neo ]; "secrets/mediakiwi/mediawiki-admin-passwd.age".publicKeys = [ mediakiwi ]; + "secrets/mediakiwi/mediawiki-ldap.age".publicKeys = [ mediakiwi ]; } diff --git a/secrets/mediakiwi/mediawiki-ldap.age b/secrets/mediakiwi/mediawiki-ldap.age new file mode 100644 index 0000000000000000000000000000000000000000..deb72607fa796afafcecf162a72b6721bb93f738 GIT binary patch literal 1544 zcmZY5`F9Kl003Ybiq&X%jyfXy5+b@bv)5#2u$9@(9a zE%OM|Y$OwMGvbgJH>(u7DwT+IXbct)6b|vCrliI00+A$Q^N?VL)~sM*I)#HmshC;s zfFN;5%Q$fob%uj+wSgDMP-B!Z5NQ=3@QQi=u_|O3p#R582~d|1wxL>L*ecSf1mekRdxcgP0JMpeEU1ygDY%NGmiY+2lpm!9LbJnFokmzZ zPnaP+K3j_MiP9X*3nvLiNC#nK0Oruah%90S^{5WCMeOm{H8nsQ2|pXaLv}glF>6is zlw4`2g-le&VIq_c!{Y=dIv9$@<#O3puTtaJf(j4di%78ypwM#!Dhrh)d@^YkGV|4DH6g!p`Eh=rAFkrrbDu& z6l!no76adsyg@iRoq9I=Iw63%&1pXX?w4eNxzqbMFoCN`<&>dQ&iCXjZaiO>Hs35d zoqVu+L(feoca~_H8wc=+SnIdy{a8BXYlT2hmJpN zx!1+rkWJprFTeTx`nuI+(_dYR54)U=9j_JCw{0$*bF;{*Ebe#a#%M^T$LqC6Pi~dGPZcicvTewsvNiPCP*d6AWzzb_>%ATg`XIGy zac}mE0`k}8GnWiuhE{LZZe4nz$H3aKX!|dCL9qY4J~g_+Xlv!<_IVF(4Ikg!7puG2 zc~9?CllDKVZRAGZRh)WJgPNU3v)xwRxj#F99N0e6GlTkM{g5Z0UYVF%=pAu?)8jBx z+%%E(2DIP5>VEF~FI2Lo@MJ^Nskke*MpiJtSHp;ng*YNd_t)1Qo%}i_@% literal 0 HcmV?d00001 From b0ffd3aded11a11c376a37a1b60762cf7f9ccb94 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Thu, 29 May 2025 19:46:06 +0200 Subject: [PATCH 20/77] Correction de la config LDAP --- modules/services/mediawiki.nix | 2 +- secrets/mediakiwi/mediawiki-ldap.age | Bin 1544 -> 1541 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index 1f1f208..9acb587 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -90,7 +90,7 @@ in $wgWikiEditorRealtimePreview = true; # Debug - # $wgShowExceptionDetails = true; + $wgShowExceptionDetails = true; $wgDebugToolbar = true; # $wgShowDebug = true; # $wgDevelopmentWarnings = true; diff --git a/secrets/mediakiwi/mediawiki-ldap.age b/secrets/mediakiwi/mediawiki-ldap.age index deb72607fa796afafcecf162a72b6721bb93f738..1a646b18d62a4f9ad2392b6a97ce619f255138bd 100644 GIT binary patch literal 1541 zcmZ9|`*YI-003YPP6q|t4jo!lEEC~a-P)#2y9x}`SK6*=(pUP>Ax)d6&$LamWU0s@ z@;Kx%1{)7IlmQAjj&p1(c*sjP#zVk4#-@YwK$Q2x;{cuW)F0(9`0lPISXpDU>?L3#=H(U zsp3e$1L%MZ2|67jLd$sdF0;vj>&!-((ZJ=5Ry83-eP~Ppq=PD*BIQYkG#CVi{?jrs zt}Fw9Y6;2fLrPu(NlC4hWb__7%IX1Y7-2P(%#o!%WL)V19CknI;K2|Y$-r=21m_ZF zhDEa`xj-oVm=@Or1dcKU1E5YyN@H;^%_iIiU7Y7s|FhJ3n+*HcN+oD%j507LRgE&N zpfD$;QOnp`4z)pX-0jYzxHm4%;{s?DL>MJ=*CGM6LspfH6F$t2*FY+xP#eqRQIN!P zS~V`S1q~WKhCIG#0gT~Vha{pvb%aa65{ySpKyHmZnzyOssNck9s~lM*=9CJM-;cs^ zGbM795wpv|+t>u`cV>uy6O$=vhbSo+QhFF9jnPy9;kX3E z5+t1u@ByQo4r)9o;!#JfYK%~@|4x>bNi1|QlVvfY*3G)2@}wUkl3r&VHyARSDq|uQ z^0|$AkKZKK<}IQ{m!|JB@3MUH);9&J5p}upu}3Oj8ZK>Qw|{!2$U7Z}&oyo6SlzpI zv`KdU7T$b&66q9Qcy9k8J-xU9I9hc0MZxg&#ovaWy6BkI z8|r`Ix9;^lNc-DcfFGaK54TR3^)4cGh`${%_1v5}?emZm>s#Lt&tK6{vS)koXzNJX z>B6R$M?KqnqwC1#&0)udNv@yyPJQ%be&y|Tm%lnZfBEc{D+>oF6R(aC4}Li1Ci=wW z&12+mF$GoqV-NR{1Eq>Vamluyph=ad+a`9+e(=|V$-Re$3l<3ld*xf3$K9>wU*Ad8 zpXpbY&*3A+*RC=9mY%Q{{<5Qc%d+IbbWf6_CqJ5A!GAV#;f$9|i!;spZ!|yOGi}xm z@tYkTMcbaUc#1_ohY9>@WoXWwAZwc z7)Mw>Yv?^*njXH{ZQD0*Vqd9YXYIgG4dVKleT%K$!K$^FUcIy1qPf!9raLq5!3P6> zh}L@BGF>IrnVFzxO?SQho$=iwb$8z+Lfiuy=NMlpxqhcFc4ySEe9VIC%NwJ6zrVYw zdfUkIhh$=I30HV@(N^pJ+6C;UePQ>X=cfX7*x*0=XuDaYQ`sBO4TYhcqsH{8i z(c9xX?{>CM@XlX(^pWfS5Vq~Q=K1eG|Mg1Yo+XcF4;(9E(ql_YPR5tn2fI|`8_!PZ zyH+a zE%OM|Y$OwMGvbgJH>(u7DwT+IXbct)6b|vCrliI00+A$Q^N?VL)~sM*I)#HmshC;s zfFN;5%Q$fob%uj+wSgDMP-B!Z5NQ=3@QQi=u_|O3p#R582~d|1wxL>L*ecSf1mekRdxcgP0JMpeEU1ygDY%NGmiY+2lpm!9LbJnFokmzZ zPnaP+K3j_MiP9X*3nvLiNC#nK0Oruah%90S^{5WCMeOm{H8nsQ2|pXaLv}glF>6is zlw4`2g-le&VIq_c!{Y=dIv9$@<#O3puTtaJf(j4di%78ypwM#!Dhrh)d@^YkGV|4DH6g!p`Eh=rAFkrrbDu& z6l!no76adsyg@iRoq9I=Iw63%&1pXX?w4eNxzqbMFoCN`<&>dQ&iCXjZaiO>Hs35d zoqVu+L(feoca~_H8wc=+SnIdy{a8BXYlT2hmJpN zx!1+rkWJprFTeTx`nuI+(_dYR54)U=9j_JCw{0$*bF;{*Ebe#a#%M^T$LqC6Pi~dGPZcicvTewsvNiPCP*d6AWzzb_>%ATg`XIGy zac}mE0`k}8GnWiuhE{LZZe4nz$H3aKX!|dCL9qY4J~g_+Xlv!<_IVF(4Ikg!7puG2 zc~9?CllDKVZRAGZRh)WJgPNU3v)xwRxj#F99N0e6GlTkM{g5Z0UYVF%=pAu?)8jBx z+%%E(2DIP5>VEF~FI2Lo@MJ^Nskke*MpiJtSHp;ng*YNd_t)1Qo%}i_@% From dbd523bc8547075b44fd465958718766e1b42eda Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Thu, 26 Jun 2025 13:19:51 +0200 Subject: [PATCH 21/77] Adding some extensions --- modules/services/mediawiki.nix | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index 9acb587..a1a352e 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -85,10 +85,17 @@ in # Disable anonymous editing $wgGroupPermissions['*']['edit'] = false; + $wgGroupPermissions['*']['read'] = false; # Extensions $wgWikiEditorRealtimePreview = true; + # Custom Namespaces + define("NS_ARCHIVE", 3000); + define("NS_ARCHIVE_TALK", 3001); + $wgExtraNamespaces[NS_ARCHIVE] = "Archive"; + $wgExtraNamespaces[NS_ARCHIVE_TALK] = "Archive_talk"; + # Debug $wgShowExceptionDetails = true; $wgDebugToolbar = true; @@ -110,10 +117,23 @@ in # Enables embedded extensions Math = null; MultimediaViewer = null; + Nuke = null; SyntaxHighlight_GeSHi = null; + TemplateData = null; VisualEditor = null; WikiEditor = null; + # Bundled with next version, put as null in the next update + TemplateStyles = pkgs.fetchFromGitHub { + name = "TemplateStyles"; + owner = "wikimedia"; + repo = "mediawiki-extensions-TemplateStyles"; + rev = "REL" + major + "_" + minor; + # Le SHA doit être changé à chaque nouveau commit de traduction. + # Pas de meilleure solution à ma connaissance pour suivre les releases. + sha256 = "sha256-fCBccHTIBJrUyxe6oP7VR84HO65ICETE73X4rswQo4c="; + }; + # Popups TextExtracts = null; PageImages = null; From 447f240742688f9105dfec647b0799e2075de5ad Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Thu, 26 Jun 2025 14:37:41 +0200 Subject: [PATCH 22/77] Increase upload size --- modules/services/mediawiki.nix | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index a1a352e..ca125ef 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -16,6 +16,11 @@ in owner = "mediawiki"; }; + services.phpfpm.pools.mediawiki.phpOptions = '' + upload_max_filesize = 512M + post_max_size = 512M + ''; + services.mediawiki = { enable = true; @@ -32,6 +37,9 @@ in $wgFavicon = 'https://www.crans.org/images/favicon.ico'; $wgLogo = 'https://www.crans.org/images/crans.svg'; + # Files and Uploads + $wgMaxUploadSize = 512 * 1024 * 1024; + # E-mail settings $wgEnableEmail = true; # $wgSMTP = [ From daaa4af268763b1a6deb931b618832b71fe951be Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sun, 29 Jun 2025 11:12:43 +0200 Subject: [PATCH 23/77] Increase upload size (for real this time) --- modules/services/mediawiki.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index ca125ef..db6a732 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -19,8 +19,13 @@ in services.phpfpm.pools.mediawiki.phpOptions = '' upload_max_filesize = 512M post_max_size = 512M + max_execution_time = 1000 + max_input_time = 2000 + default_socket_timeout = 2000 ''; + services.nginx.clientMaxBodySize = "512M"; + services.mediawiki = { enable = true; @@ -102,7 +107,7 @@ in define("NS_ARCHIVE", 3000); define("NS_ARCHIVE_TALK", 3001); $wgExtraNamespaces[NS_ARCHIVE] = "Archive"; - $wgExtraNamespaces[NS_ARCHIVE_TALK] = "Archive_talk"; + $wgExtraNamespaces[NS_ARCHIVE_TALK] = "Discussion_archive"; # Debug $wgShowExceptionDetails = true; From 82f868d63e6d9132e2f9309b9dac3d894db9e073 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sat, 26 Jul 2025 23:58:57 +0200 Subject: [PATCH 24/77] Ajout des modifs de lzebulon --- modules/services/mediawiki.nix | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index db6a732..042f516 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -26,6 +26,8 @@ in services.nginx.clientMaxBodySize = "512M"; + services.syslogd.enable = true; + services.mediawiki = { enable = true; @@ -38,6 +40,11 @@ in passwordFile = config.age.secrets.mediawiki-admin-passwd.path; extraConfig = '' + # TODO remove log error in test + error_reporting( -1 ); + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + # Server settings $wgFavicon = 'https://www.crans.org/images/favicon.ico'; $wgLogo = 'https://www.crans.org/images/crans.svg'; @@ -45,6 +52,10 @@ in # Files and Uploads $wgMaxUploadSize = 512 * 1024 * 1024; + # Limite mémoire, quand on fait de gros importi (9M de xml), le parser + # prends plus que 50M + $wgMemoryLimit = 512 * 1024 * 1024; + # E-mail settings $wgEnableEmail = true; # $wgSMTP = [ From 340b79f78563a1b381cb37739f678b3d13400f68 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sun, 27 Jul 2025 03:01:31 +0200 Subject: [PATCH 25/77] =?UTF-8?q?Ajout=20(maximaliste)=20d'extensions=20(i?= =?UTF-8?q?l=20faudra=20peut-=C3=AAtre=20en=20=C3=A9liminer)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/services/mediawiki.nix | 55 ++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/modules/services/mediawiki.nix b/modules/services/mediawiki.nix index 042f516..0b6095f 100644 --- a/modules/services/mediawiki.nix +++ b/modules/services/mediawiki.nix @@ -16,12 +16,22 @@ in owner = "mediawiki"; }; + environment.systemPackages = with pkgs; [ + imagemagick + + # For the PdfHandler extension + ghostscript + poppler-utils + ]; + services.phpfpm.pools.mediawiki.phpOptions = '' upload_max_filesize = 512M post_max_size = 512M max_execution_time = 1000 max_input_time = 2000 default_socket_timeout = 2000 + extension = ${pkgs.phpExtensions.mbstring}/lib/php/extensions/mbstring.so + extension = ${pkgs.phpExtensions.luasandbox}/lib/php/extensions/luasandbox.so ''; services.nginx.clientMaxBodySize = "512M"; @@ -51,6 +61,10 @@ in # Files and Uploads $wgMaxUploadSize = 512 * 1024 * 1024; + $wgUseImageMagick = true; + $wgImageMagickConvertCommand = '${ pkgs.imagemagick }/bin/convert'; + $wgStrictFileExtensions = false; + $wgFileExtensions[] = 'pdf'; # Limite mémoire, quand on fait de gros importi (9M de xml), le parser # prends plus que 50M @@ -113,6 +127,12 @@ in # Extensions $wgWikiEditorRealtimePreview = true; + $wgCiteBookReferencing = true; + $wgPdfProcessor = '${ pkgs.ghostscript }/bin/gs'; + $wgPdfPostProcessor = $wgImageMagickConvertCommand; + $wgPdfInfo = '${ pkgs.poppler-utils }/bin/pdfinfo'; + $wgPdftoText = '${ pkgs.poppler-utils }/bin/pdftotext'; + $wgScribuntoDefaultEngine = 'luasandbox'; # Custom Namespaces define("NS_ARCHIVE", 3000); @@ -139,13 +159,32 @@ in extensions = { # Enables embedded extensions - Math = null; - MultimediaViewer = null; - Nuke = null; - SyntaxHighlight_GeSHi = null; - TemplateData = null; - VisualEditor = null; - WikiEditor = null; + AbuseFilter = null; # pour faire de la modération + CategoryTree = null; # pour faire des arbres de catégories + Cite = null; # pour faire des références/footnotes + CiteThisPage = null; # pour avoir la citation (à la bibtex) d'une page + CodeEditor = null; # pour éditer des macros/scripts wiki + DiscussionTools = null; # pour des pages de discussion intéractives + Echo = null; # pour le système de notification du wiki + Gadgets = null; # pour avoir un système d'outils activable par les users + ImageMap = null; # pour mettre des widgets sur des images + Math = null; # pour avoir des maths LaTeX + MultimediaViewer = null; # pour avoir un affichage sympa des images + Nuke = null; # pour purger des pages + PageImages = null; # pour set la bonne image représentant un article + ParserFunctions = null; # pour la logique et les fonctions de base du templating + PdfHandler = null; # pour afficher les pdfs + Poem = null; # pour afficher des blocks de texte respectant l'espacement (poèmes, écrits) + ReplaceText = null; # pour du méga-renommage à travers le wiki + Scribunto = null; # pour faire du scripting et des templates pour le wiki + SyntaxHighlight_GeSHi = null; # pour avoir de la coloration syntaxique + TemplateData = null; # pour faire de la doc sur les templates + # TemplateStyles = null; # pour styliser les templates (bundlé pour la prochaine version de mediawiki) + TextExtracts = null; # pour set le bon text représentant un article + Thanks = null; # pour remercier des gens en notif + TitleBlacklist = true; # pour faire de la modération + VisualEditor = null; # pour éditer visuellement les pages + WikiEditor = null; # pour éditer le code wiki des pages # Bundled with next version, put as null in the next update TemplateStyles = pkgs.fetchFromGitHub { @@ -159,8 +198,6 @@ in }; # Popups - TextExtracts = null; - PageImages = null; Popups = pkgs.fetchFromGitHub { name = "Popups"; owner = "wikimedia"; From d5e49395b984c46d8ac6e5f88bf7c51e1d137d5e Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sun, 27 Jul 2025 18:02:10 +0200 Subject: [PATCH 26/77] Erreur de valeur --- hosts/vm/mediakiwi/mediawiki.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 0b6095f..973182a 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -182,7 +182,7 @@ in # TemplateStyles = null; # pour styliser les templates (bundlé pour la prochaine version de mediawiki) TextExtracts = null; # pour set le bon text représentant un article Thanks = null; # pour remercier des gens en notif - TitleBlacklist = true; # pour faire de la modération + TitleBlacklist = null; # pour faire de la modération VisualEditor = null; # pour éditer visuellement les pages WikiEditor = null; # pour éditer le code wiki des pages From c9aef08b0ba06afaf4cd390882724d05b096c9e2 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sun, 27 Jul 2025 20:47:20 +0200 Subject: [PATCH 27/77] Secrets fix --- hosts/vm/mediakiwi/mediawiki.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 973182a..79100f8 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -7,12 +7,12 @@ let in { age.secrets.mediawiki-admin-passwd = { - file = ../../secrets/mediakiwi/mediawiki-admin-passwd.age; + file = ../../../secrets/mediakiwi/mediawiki-admin-passwd.age; owner = "mediawiki"; }; age.secrets.mediawiki-ldap = { - file = ../../secrets/mediakiwi/mediawiki-ldap.age; + file = ../../../secrets/mediakiwi/mediawiki-ldap.age; owner = "mediawiki"; }; From ab2990df71796091dee54b3b1f8f508e95d42097 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Sun, 27 Jul 2025 20:52:52 +0200 Subject: [PATCH 28/77] =?UTF-8?q?Rechiffrement=20des=20secrets=20(d=C3=A9s?= =?UTF-8?q?o=20pigeon,=20je=20suis=20les=20ordre=20de=20lzebulon)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- secrets/acme/env.age | Bin 1414 -> 1414 bytes secrets/apprentix/root.age | Bin 1004 -> 1004 bytes secrets/common/root.age | 75 +++++++++--------- secrets/mediakiwi/mediawiki-admin-passwd.age | 34 ++++---- secrets/mediakiwi/mediawiki-ldap.age | Bin 1541 -> 1651 bytes secrets/neo/appservice_irc_db_env.age | 39 ++++----- secrets/neo/coturn_auth_secret.age | Bin 1079 -> 1079 bytes secrets/neo/database_extra_config.age | Bin 1187 -> 1187 bytes secrets/neo/ldap_synapse_password.age | 37 +++++---- secrets/neo/note_oidc_extra_config.age | Bin 1555 -> 1555 bytes secrets/restic/apprentix/base-password.age | Bin 1235 -> 1235 bytes secrets/restic/apprentix/base-repo.age | 37 ++++----- secrets/restic/client_env.age | Bin 2075 -> 2185 bytes secrets/restic/jitsi/base-password.age | Bin 1235 -> 1235 bytes secrets/restic/jitsi/base-repo.age | 38 ++++----- secrets/restic/livre/base-password.age | Bin 1235 -> 1235 bytes secrets/restic/livre/base-repo.age | Bin 1081 -> 1081 bytes secrets/restic/mediakiwi/base-password.age | 38 ++++----- secrets/restic/mediakiwi/base-repo.age | Bin 980 -> 1090 bytes secrets/restic/neo/base-password.age | 38 +++++---- secrets/restic/neo/base-repo.age | Bin 1077 -> 1077 bytes secrets/restic/periodique/base-password.age | 38 ++++----- secrets/restic/periodique/base-repo.age | 36 ++++----- secrets/restic/redite/base-password.age | Bin 1235 -> 1235 bytes secrets/restic/redite/base-repo.age | 38 +++++---- secrets/restic/reverseproxy/base-password.age | Bin 1235 -> 1235 bytes secrets/restic/reverseproxy/base-repo.age | Bin 1095 -> 1095 bytes secrets/restic/two/base-password.age | Bin 1235 -> 1235 bytes secrets/restic/two/base-repo.age | 37 ++++----- secrets/restic/vaultwarden/base-password.age | Bin 1235 -> 1235 bytes secrets/restic/vaultwarden/base-repo.age | Bin 1093 -> 1093 bytes secrets/reverseproxy/.gitkeep | 0 secrets/vaultwarden/env.age | Bin 2951 -> 2951 bytes 33 files changed, 245 insertions(+), 240 deletions(-) create mode 100644 secrets/reverseproxy/.gitkeep diff --git a/secrets/acme/env.age b/secrets/acme/env.age index 72ebb92fc372af3526f5832fd7344fce75f75d5f..b49051c0ab072a48412dc64f1889e330e5cc01ff 100644 GIT binary patch literal 1414 zcmZY7+p81>0LO8Pl31#T2)m%Mfe|Azd zc0o$fFe*q9ED9+!lU^jqEP6OrMv|lyr$UmGT^=GLjMAW>GI}Zf3qJh#eGS`MutsJy z8z*is+89JOIE8Zyi}^Tq7OXx}EW&u+mHQ=&$a`f?nN!R_m~=Y4u91f48UVre+Ju2l z0dmGokr1r`93{7MO|k%ICOT$Z-EcTw_5vni0xBujBMAUvL~qIL5QG8}; z^O8pbl05TfES$iFX~lyQtm#IHRODa*8qRGe6)JX%#O*j|Rc48T>P>}m5WgIX(IAiu zQ!+{uk=G@zG;GTjN?6D&3a-^?%2ahCZ!;j*2r_tBujp}gil?M2dVzrz;-FC;j5%Xo z3j2}Isx=~4lIWtuCW?SK6-^h*GBeKV0u?l=PNCx^b%{{snrw?Hnf{}V-RZ&@DPnmm zrOZN_CtRjsCG;F8f~bwKG3=CmqNa2mQ=ErIEW-p7F_M7~fdrh(Gy_O*x}n8%&t%96 zZzQCcinXEK4=p?iRnG}o7fHHZ;(r!K31IW8g_x9pBq*d7%;C^BL)gc=^J`?DEL|-lbelMDU^$ie|P=25EiNS zvsS(c*!?LFj*)>AqNO^d5q`CfdvK=s&5W0;C~H?np*|yOYz`KOa!>KgC||6^Nlk$} zqQvOcIOMxAM6g852lN4}>PfeJ?Pnb5N|v2x1e;)|kvCv^NzAxT%i_6uHh?JHZn6+2 zrWglJLQQihB?o8>A1~=p273%x!h*zxv!Z5_Ar48EVu1s>!iZH&#}lqEr0%roG*1Ns zQ#W9;HnJrx<_a9zBz!d`RdR~X$AyZw#Mb7cakEhqbDd}kS0-Iit<(LhHT9+we%8@I zW9SGvpiSCsXwoip5Ring(=1&i5+Yv%5sLxAYPJ-jL6=&>FbUApv@~r8hN_K{`9#(d zPa$0x%2Aw*yEqU96O0j5x^2)s-AkRIZfg2vWra z!BP7XkVS-FYY(p0zp#)e91JWC=vD>i2+=Rc0fjO(z^~eL3~Gsy#!xe0WeoOON{+wh zzB@(#>n+jrsb>4zP0jHWZ$0)oYbS$uUwqwnpZ#S0w(n0iln2)??%6rox7Ee?+iUHc+bk_{g)fh??3lv`tZ)L&i{US!vpC$_RQwl>W}X~xsE;Y z<>kBf?mn})qI;*Gd2Y4(DR8iU*1CcTD?3&{x_gN@a?9a&ehXI)-Z;N?*LU@c%a?W+ ztzU$nwjaOg-e2F`Hr#&fkq!ORkD5>ad}YURYuC*Lwo&>JTYl*1+CzIc9eGV)`=7P8 z9vU4LfHiMyv33B;h2h69?fd5OFUrDuFCS=M;%~eEg;S?)|Kr$+v%A`_uKsoIz=ehL F&fj1+?EL@$ literal 1414 zcmZY7@9Pr<0LO8Qgj&CR&;tX7eWCh7=e*r*_lqdH+wJal+it&Zx7!uL?)P@PU%TyY zyHE?#f*uH2T1HTb(U%@b5tyh{LYf8^SV$q2s2QY0gh+*uJ?!gW@Z$a9Gq8-D;c1ga zFdh2dW`AOVF_g(Yk&PldXXr43Kty&Z>X<=hS*=k)xgypnkI5cCPFbQpCD~Y$C``xoxvtjcR1p`AVza+mV@sMOli4PsH08WcSVWH~VXa!Jkz_u~?6c!tt=&Cc}=E{Ley5HL!WpB9H=C<~c%VFp5Odc8rje?-*>K zDxj#T@Dyl-Fvh8Qg2(`}>QFJ8plMP|?OCCI6M{W$7#p>I5RDdGti$4j(V%vI^@^jvL(bu#QV5-EUzzAGRxBv%98#iT*1 zLUg1y7^T>q*<{8=niSSdJ1t1|VM8f|lbT9W5;FFnNfb{7lpKpjnv5X%mNr(fJ8Y5- z7JLE%NMYEs;e?EdUaMUXEmj4oh34ueX(m0}S4^Cy`k7iS@ESrs#c&HG@Osa76t9x+ zHl-dg6N!lBK+Kuaa>C)x>Lm@iPEN_JVw7pBNc);Fo;pOMF>1rZ8f%ts#xiA@t}7GY zp6S&=*Y0OV02?HcJL~g84956^ar%o`SELlE`g4IG=~Sm>A(__4{%Q@eWnXJ$p<)w- zXCAhg*h0lKgoWTv^+}p1DKxZO2->kh&_GOyhn!5M&DCd3Q7GsV&RT3!5p54gYwE!A zZNM}&fhvexS2XP^p2_8M*)r0>+8wH1l9UcFD{$y)xNfx|Ep~%hI_U$JDtTQY7OTaK zcK@ce?|lBx6ghJ2uG{u(IeUWboaRsMbT4(zR7ICkTKJ70p{3b#-G-n{(c_eY=Fu=$VuyMi|_1E)VZci;tl z|Gwo{;-5F3ihlfQ>5&ibdE*+sZ|CcSr8B3m9KKrEa_J)f=fTV3yNA~uZ*w8DVe2!6 zttVHu?cVeQKwV$|QGVBn_R_h1FW=a9_s5yX&L95Z$hGjrce_}5wM>&N3g->vy&=bE?AJio*H K=K7b(eb&D+Me%t6 diff --git a/secrets/apprentix/root.age b/secrets/apprentix/root.age index 97706aef90355a432bc2fb2afc0fdac99824cb8c..c6541cad1a54f12c08a262282277a3c33991a291 100644 GIT binary patch literal 1004 zcmZY7y$-8n0LF1=h7)hVgu$)W@>$BF;TsC2e3n9VDD(y1@)p`cfs(lBV$9ieG0}T) zH7+Kecmd9ip3OlMH8Ih|qmv%5;Fmnl|4@KAsI%MS(!r*Fv$++Ep2X&F;cZ*a!I5HF zlE<^eRLVLVbwceOYis)-DsYW?pfKz zv+A-H;!STPO$pIdBA~-Uf|wl=%E(?cXYj4u?fUD@AyBX`gppm`(Q@e|_xO+;RKezWIT9x~&2I6DYxf2>k%b)H zW_rg7R1I81-WOOp#JkmgwAg5Gt({C^z?iDn2~zfCd*LO5d6A`1MFA(gtgRQ&0|gEr zN6(yw@qE~U2<+?A^g!KI_xx;C>%t|5vw)4zV_AnGR@OOVY{z2F+Fc3VYDPBN=6FoA z+LO%s=<3paYvRKy|;;A0?aj+>Om%vgls9R6eZucsb+cB{ZvT19oCi-n@isF7smVt9E zB9{KAk$>0Emis1iacVVNsl@0KOl>H=o=IC(h1${(3{p)5cwBWdF_EOvMlrUyh7+Ok zrg+w)(ol3SmKdg9o%D^{b;`ZJroL~L2ZvHzY(AgkyIPT=9T$W$&x$cdi(vazUP1Wbn))brZ&%Gs#9slYE)UOeTlPVUpcU4sXuK$#KbZfWjmU437703C5?^8?)X$tDU3n#MUe z@K2HqV0N}tloEW?W|6Hwxv_E^PHfp*1%K4(vH+lDBVV713_!u6dHs+}qppzm7>r;q zWL(QvjGH2P=ft7CJq)7$P*~DOtx3mCL(mKyO+r9UFi)qs0lL_qE@j1BRkLZ*xVce2 zd*d$WQw{uW&&GI`amEC(X@n2z&|-_Ijs(_=ikg&hcBL?yul%9fW6p@T!d=FK|5%K0 zMD>3y)V-kYVs=;DsLNm+D@H2?JvTzr^K?X#+q6TqX{vFYg7!2*O&2gtYMyF_N zTAMd=xx08h?{aPprPgz11h9`++!Inb8?fvK>J_PWeWVF3%V1GvTiQ}p;>~XKdZ856*KB5$+vT^rO8?iNFO8fux?kiV?UK*0d`_ zyVFFQHV|=){?{g-lCwO)*UGEX{h|saV|Er9U!vfwBSmmK7e&|YEk>>Ox!pa*i5fbt zqh(m3Fd+&Js4-7~>YPTC4#(ZWR=1fn=wcV}1Ba68zyxYn-R44u*-$Ikl%(lG-VrepVz3k)dyku$P8Qw5*Vzu)U z&U>uU$)yeqc%b5t2TSi>GW593%d2|c9l1J22Tchz3rAFWENS(?x&V^Pr_{a0=iX5r zwr~ym!7=j(8S^B0Z`2Dp9ZO>cwZ^YJwApN6Vb&hjEmy{-gourFWm9rI&nL`BJCmE_ z;N#nUHv2f*Loc5E_|*sgne(Rj>DNELefit(?dPAo#lQIT%h%GYufMThf2w@e)A#33 XyuW`=Uc7I ssh-ed25519 2k5NOg cR5ohdfnKc3NNlGxdVDsLe+jAmLBIfOiaGfFAPPmrTg -848H6k08p4PzbkD+c4AdtGAK0D3fxFFOEzrvqvFDgbg --> ssh-ed25519 iTd7eA AWwcHOwmn5hHn3POEFF5pJpvWJ8lcbrMe3n3JqBfyX8 -D2PdIh6BFBocjfNeDLY6f9Th0yixTBp7V6sMVEzoXjY --> ssh-ed25519 h5sWQA pWqJ5nuVHkg6rFvGn+8tkdH/cKQ/xwIMED/giZeCOGY -GOKAPL951GOkyQxM2SEst9Yv7Omhp+y22zW/Vbu0x0Q --> ssh-ed25519 /Gpyew kRpo5sKEN283fFQpr2ML9GuhpugiqRsQ0Ezc4BjBKlQ -8hKJmmgoNapIruh1hc+EUyB/uZVKvtZrocPPd68naMk --> ssh-ed25519 FtI9pg Ieby+KtL7TViX81m58F+y1Ll3aZubWndFBOpVEEr5jQ -JhpUwA+U5ppm+SCPzQ0JNA/hjGGUGd+6xpDenjPsnt8 --> ssh-ed25519 hTlmJA HTUuHAiK0jRB86m9kxk+K/U9b7cnTLwJ6DizUhpNOjw -T6VuoPzd196TizLAJgi4xa4pMXlF8nzrbQMENRbRlY0 --> ssh-ed25519 GCcVXA 9mjW03T5ockAAAtZtVjIZTIYOXwaCeG4nAK+K/97EDs -yYYVpyomBECUSw+huUaX7p9TdBclUukd0m1tZbrOP/0 --> ssh-ed25519 LAIH1A 6aYzD3onWE/lZE22Y+ZRcXqZX8ODq8gM84fvtIuG3ws -kuNjmtOxsCC6xpsMpDN+d5/nmKgKo5Q2n/NvVEJGqVk --> ssh-ed25519 qeMkwQ UkTD2aRW1DcE1pwwcUY5jdzFry47IOfrgcFeb/7U0iE -0K3bYslOGMvhvD52C/OYKWLCSD/GFYUnbAMNGOwJ4O4 --> ssh-ed25519 TqxOLw ucyZoaPMI+iC/lC8fdZlSwL70ScqA/18rFgZKGrWbw0 -fNLP3zECUQUOz33Rf2XZLHiY4uSt6oc0z5U7x0GBmtc --> piv-p256 ewCc3w AocWW7SEg3MVI/sCjCHu9obVVVVbFcfFazpmTTR+PRMj -498jlz/DJgqzZxmvF6sRHruaOK9ssXpuM1UfbZwzWE0 --> piv-p256 6CL/Pw Au/taJ3kM3uj06PdSgUPHC6UVCCOYDbMY2m2Eofbi63V -9NRjQxdkGM6lNnRvqWyR7ugweuvw4R6oCG2Qm8JBPuk --> ssh-ed25519 I2EdxQ nek4QEs28RjB5LmGI8QmI+PovaBsP20H58HLft9t314 -7kD4VKhSe8GVI6G5nEEB9S75nqiWcw2KIq5yXQW6pkM --> ssh-ed25519 J/iReg NaSxlV2jBjaEHjddB2x5wiUfu8dqzgPOKB1vaRo8cUY -b2Wak21uSh9FEcCsXAc9zjuakI0B7e2D4j1EmyKHCS4 --> ssh-ed25519 GNhSGw /0K7Q0S2PLsIa4gFjFpwcXYHhRuDdRJH16FMX2/tJhs -ti7iUykOROYtos8+jPlQoyCur3hhgZumzzaqHwE/k/g --> ssh-ed25519 eXMAtA wx6srIjvJ9E+lZzvg6jvtAVmTg/0B6x6f1zv8D6LuEE -iFjLVZ/c/pevSRzoBUee07TX2/tVFUThvvP6Bek4LIQ --> ssh-ed25519 5hXocQ an1+7W1qN6bEdGXBfio99DzkTcZC6gEOm2ZQe11x5Us -cbPU+Ih4aUVSOOveg/mQnV57Tn8boE4CKusOag5ZgNU ---- Ya9fvOnvystGbDpL8ti3cqD4sNIaMNF1Yv8OuviMgjI -Vj&qnZ6#;8BCGE1Wsb -4. 0cjA]I#M \ No newline at end of file +-> ssh-ed25519 2k5NOg /QDpcGLUswG5B/VgK4LK4TA+eOuwznsW1Am+hvNHagA +JTFogIDwh4sEiMvTaH82wSJhy2s18WM1Upb8e/ry4Oc +-> ssh-ed25519 iTd7eA FvyK9mjTJG4szXjK7vrKE2is8AKB6bLLAZ6aU3R0UkU +TCz+hgORz0RHlpNNLbg21JBMrnObwJiIv0mNJLyvIVg +-> ssh-ed25519 h5sWQA p6pmwQKE9AabnIJwK7EU8XY+cQlIGQhLfuYnRBEwYHc +LzR2A9+QX3Z2cArfzPzHBqVHjG7rZ/9ltNexpdETf1I +-> ssh-ed25519 vZ8Vgw 9Ce/JMUd7yYLHg+F+uLmYdGaMlBnF3ld1cOjpID0kmI +rH8+EHV1QgWt+h9cIynwcPVGh773IeDeZd0n065d+RY +-> ssh-ed25519 /Gpyew j1Ex/u/KijHBDWKAHBVCBn8Ok1LD/86eRLpj/HTn9WA +0+m28W6v1mkwwVhIjUY6dfbV+cQVYkUAuSJ/iRBmdho +-> ssh-ed25519 FtI9pg kVnavpknVmp+fEsNZIum95gOd1H0xD6iailLvq572Ag +XRJtTQ31hnBfCszFTllIkRlJx5ueEhUuh+9Iu+npRsQ +-> ssh-ed25519 hTlmJA vNEda7NLx6uCwawvG+q27zanZesW6YEfj2IWXQIeZRc +anEmC5fxBv1ZWLOwa2zZGmR7lQSoDNTDYhuTmLtTprU +-> ssh-ed25519 GCcVXA EUnNiwe8I9t9UK329sH24VoxsE7WAEZ1IUM/veux6hU +VxyYUUHMoFrix2JH4DJXlDg7PBfxNfevk7cNhsSnRrg +-> ssh-ed25519 LAIH1A NkZXyPzTX2U8PYZVf3g3XjfjRPpKjVr9FngU9m47EH4 +RsgRPseog69lzWVlF3pfwWQOcbLlfaRTxQ4aqiuHiCw +-> ssh-ed25519 qeMkwQ hGhCQe85f1pi2j1HQDtch8npmwVLuYI02yCEcebLbTU +wGO0XUz4WdiVVsWAAwluJYfj/LNlRAlp/dNpCBQ1t1Q +-> ssh-ed25519 TqxOLw AyneWoxUjnyhQ8iwEl+S/mrftNRwXJNYKAAPZ5LGpw8 +oyNC96VE9BdXKaQZEoJrzl4VVVuKFiimu0sf4LZLfqg +-> piv-p256 ewCc3w A5Wv+xoc5Ygf820u7+Wo5A86sGequUy2vZEj4D2dXxeu +JmwhtlzJjed6FEvaxgrGOYXnxlS15Dc8wmPNiX9Bhh4 +-> piv-p256 6CL/Pw Ap8y+fXFbHNcXcY78uA216V9FaX54AwcC67HBlBOdTwM +YESUdJ9S1jLfJAFOajzO5kb2djgEIh1FUY8ss5Sq0gI +-> ssh-ed25519 I2EdxQ W2KxcrDn+kFJ0XcaqFwZeCDFzQxiTkBerTHP4UR6WHA +l97139ayTSmJ82bE4VMENRP8G//dRh7/bT7n0V2eBHg +-> ssh-ed25519 J/iReg x7SEySFqydZiYqa0woj8U1Muiq43q5GvqjjIoJzBk34 +6gneQWFkj7wsn/oU9Qb4AABsBqSFgvbpNCKy+jki/1k +-> ssh-ed25519 GNhSGw Loi1i2EE4soo7JIt0C5YAFlnXexB5DK1sJj5cGjtuV4 +qv+fgbNYaOJSrJYwy5qEkXYsA/TrK1l6Rt/hI06bpOw +-> ssh-ed25519 eXMAtA HdujaNPBqwW6OLR4Cf0cHPZTBAxyj3v/cVU8PefLcGc +t8gcVXDRuu+9NvmP97h2V1LJeuMjZXG73zguMSUl/W8 +-> ssh-ed25519 5hXocQ ih+FM0WB3Jxf1AVPPurl0oi/xFxpZXMc92bkuOFBNEY +3buzyNxyiRASsESZvxWFmOD0pAuzsBbltU+6iPNjV1c +--- Ts6Z6554UxYnw09pwL1xtm9AV74LWZiEOZXWK9NVFOo +҆"{obfШ} ssh-ed25519 vZ8Vgw oEYPtmVUnoOjjiYyB/k1K70aTaUc8yaFKgnftR/vRls -20sp0o5Bi8ebZVxltOu8JAFS+7pRLZnkvI7Z46BBmtU --> piv-p256 ewCc3w A/amtNzHFy9pfbpYyu/PHCt0Cr5BGAUpyVb2ll3nX5VG -wPsC1ZZOyIOhWJmT7mKrp68RlB6RgfObULJ3kAXA+f0 --> piv-p256 6CL/Pw AiURYPR5gbbz8EW8FbcSZPL7qfLeUr6MReSFPZYRR6k6 -IOfrVsSGWmvDqW0ur8Rb0G9q98Qbc7Q9Hcw+J7twKeA --> ssh-ed25519 I2EdxQ cw4/OfxXByu8afEzMGNDji9TkZ7SpqjG3tFsgMw09T0 -b0qOsH70/DxhOQ2jpyeYliIql707+PacbZykqXkjCaI --> ssh-ed25519 J/iReg 63ftKWZoU2/CI5338/bN99Npw5rNCRtptcUdWGSVTgY -AokJ3oTUGe9BHstt6MnwQZk3w9ONIVfkist1ZPlBGXc --> ssh-ed25519 GNhSGw rcUHAE36kMf2pv2dNP6oVwB8Xz1qMLANiRSNUG0bQAk -5KhEzAAgDEwDor3xtLMuF7CSfBWCfoykROVabjpqQIc --> ssh-ed25519 eXMAtA 0j0xZb7QWe1YqxHKncTyoMiDR120F8k+DvvNmedeti8 -kf1DjOIO3oJrGs+0L5OmFXb0Or0HAOuEjUci3sHIidI ---- ZxETwXAvw7vFD19/JKxp9kuIcODkoXWJW3HDKZZ93IU -d~Ss@1q-?$Ug Z'xbb?vh=s \ No newline at end of file +-> ssh-ed25519 vZ8Vgw yOcz+ZAlhf4KeSb3tF3q5Wg1uHaRQ93pY9kI0MnoqBM +FwnjEr4YlyFqWkSnpT7ui+GbMLfLsyjibnhr850ha8E +-> piv-p256 ewCc3w Agp5uCjfrNO/MEW6QDaWXE9/3XXfJ/wRGnPaNQsxH5kU +lV1Rcd2Yrdxwnb66FLKnkpz/UXdwnZG2p2vS+rS7QOA +-> piv-p256 6CL/Pw ArqCgkL0Fyd8BsIDA9G1J4lTw3RIWKYUxksntvY5gqCh +A9NrcMolzB5WhjRjkHh29mG8u7wWsTBwtIYG55WHgoM +-> ssh-ed25519 I2EdxQ DspOhVTHYSR8NmaoMABxWBJwrPU6l5/Shg7MFO5upWM +86IDfoSZsHlVAeik5mKsocKX8fgbxyxkDTr/SxE7uBI +-> ssh-ed25519 J/iReg W7CxjkYTB7uzsMWIwVIGCg2pH/v69UFTbp2J5ssrjQc +iIAE4tHVTijDXVo6vBUg046NnpjH61gdIQ5eViqTSrk +-> ssh-ed25519 GNhSGw ghUAibPQVo2//mzSBt33LIczzJIQrU9Z9+vuSM0kuFQ +baLeilviulJJT21mYRdCpTuFpEui9B/QbVNBJIMm2l4 +-> ssh-ed25519 eXMAtA U419VRp3+uohf05v/rqQ7KtrQYDAt4ShTnssbiQOow0 +VAQXQH8y4h/xe3AeEjAIADI4uegXmlGdJCz0bW5uh38 +-> ssh-ed25519 5hXocQ b2nNj07kAm7YFBNo60GSUSsXCLGTv5RhXEnY0MBBr2Q +ApMxBMg1ts0Fism0NBplcy8qTISlKJMNI/sbNY/JFzE +--- 9rdMPy0JgtUUr/GhQ80smNKyAVFmkf/Y+UOYHPehlEg +KtuJHg ~&:&h=R~z}NX \ No newline at end of file diff --git a/secrets/mediakiwi/mediawiki-ldap.age b/secrets/mediakiwi/mediawiki-ldap.age index 1a646b18d62a4f9ad2392b6a97ce619f255138bd..889f7285e6eb0899aaec6896cfad37e4c0a853e3 100644 GIT binary patch literal 1651 zcmZY7`*RZo0tave&V}-cNVjU+|gl zBf-!*I!B3>e2Eis&k7;~NDV68bLwJ|)zK{w3^yQZDQ=E3vf3^Nd23m6D^$3E1odva z-&0NEh=j$xco7#;fCm#Rr9_wp-Ks(~2wJNk)j*W(1%{_M+#q{Xptss2`_Q7z9TBUp zFkcl?BK5PD=OkSof{kigwo!UnZOMClW+%mlo02kLC3rU#WiW+`=}Y<~=#P03jA=;V zfE)`M2$g_qZB7cOO-TnKacDG zbkGtB$bP%q&4~_&3@Hj__a;+lh6sd7Ck$A9B~Q>MsH&LFuEpV0AZW<>@vO0&Efj27 zR!)Fr0gYr?4khA1i(o?nk$YQoLyi_Dq!uU~!Ly1ed0+_6@^pmO>KRT%2r*!+mSW+Y zMEhZ9hVVhMs_bE+<_ri1>;=y3b&J+a0m8~aKE^W;l{Jd_ysHIcd0#AOxL5N498NRp zkOJ9QnsPLOAx{A^LZ!UK=0kQQ6M#vwXfE-ViWnljIQ#F-QYLqRat8biRbmpPo@xkV zUcVVMAq11eqo6ScWJ8H~vM5?C_iCu0B~V$NC@ZEM=M}kRG?~^%X^@Dda@As5(58G5 z!38;U8I;O!CWWU|8K1czDNaLN4>7XMUI|(d1d8V%E1ry?ptcBdr3z}zWCZ0utNr&M zY0_sYS=2><&lfpXVy!8s)kp{8N;DNMlVZgb%W*-_SPd%9+>l7)1n7bs z(PA=Fc1tF6(!=IkT2fIVj8({F)xd?)h`|0|Hkc&^Dx}sz`brtdnUf6W$Qt;vSEdxw zz{m0tCq}wraIBiMW+@4WG7%MHsWiY=doEw04GwG27B^xJ35E0i6391M?M+E8i?L3bvY7ViFR%D(DW{p9DDl|Iqz>C|2| z8N+WP({{8za;;-S;r)#h?jHOzGOY3Q{eu_s*$!gxXxm9o+{E6oclU)wyy4c+u`9E z!nBX3pJg80Sid3>?wWY!Q*qtkKW4Nj{W(p~w;PKGdr#R5zg^jT%=1EJ|9ei$g_<=D zvj!$$nwjnO<6li3n7(?U5~~?HPF&u3Vb1o?+NXZM!*}%T+*_MgZQkFysCDvHm`2Op zU9WXDIr~S3V)N_kKYHVZrRGU17LNWf5AB}=BS~z<@A{^t4%fEd-`Fi)X?yjH^W9s$ zQ^uAbn4H_TVAV5gN$Nmcx$^N#*T&uMzP4oENX^Ks^IP_G&V9HyIBj{&_N}M3tPjD5 zHYmSrpQ=ANfIjioUmchB{YGCq_zsA@Gp6$|XYNSrf8D)r=IOKDum7|DknvZ$7gW5; zDkJ0QX&&zDN<4UXXw7=f^~KS}s;9qsrlZe%1idwJ&EdDwnz3Dzo_tq=r@k2OJ!ZTZ zJ2elOF_%ArbbK9IUMmeXzVzHJ)O$wWJ+4l7-SyS6N9KQt9r=*VJbtmd=jKn(&l-tG zx<5r4(SzjLF*AQZOs)0?wy18ePxsHi_{TXtTB{LRbYgJm^ShCov2?cn?^ii{+F0B3 EFFbjTvH$=8 literal 1541 zcmZ9|`*YI-003YPP6q|t4jo!lEEC~a-P)#2y9x}`SK6*=(pUP>Ax)d6&$LamWU0s@ z@;Kx%1{)7IlmQAjj&p1(c*sjP#zVk4#-@YwK$Q2x;{cuW)F0(9`0lPISXpDU>?L3#=H(U zsp3e$1L%MZ2|67jLd$sdF0;vj>&!-((ZJ=5Ry83-eP~Ppq=PD*BIQYkG#CVi{?jrs zt}Fw9Y6;2fLrPu(NlC4hWb__7%IX1Y7-2P(%#o!%WL)V19CknI;K2|Y$-r=21m_ZF zhDEa`xj-oVm=@Or1dcKU1E5YyN@H;^%_iIiU7Y7s|FhJ3n+*HcN+oD%j507LRgE&N zpfD$;QOnp`4z)pX-0jYzxHm4%;{s?DL>MJ=*CGM6LspfH6F$t2*FY+xP#eqRQIN!P zS~V`S1q~WKhCIG#0gT~Vha{pvb%aa65{ySpKyHmZnzyOssNck9s~lM*=9CJM-;cs^ zGbM795wpv|+t>u`cV>uy6O$=vhbSo+QhFF9jnPy9;kX3E z5+t1u@ByQo4r)9o;!#JfYK%~@|4x>bNi1|QlVvfY*3G)2@}wUkl3r&VHyARSDq|uQ z^0|$AkKZKK<}IQ{m!|JB@3MUH);9&J5p}upu}3Oj8ZK>Qw|{!2$U7Z}&oyo6SlzpI zv`KdU7T$b&66q9Qcy9k8J-xU9I9hc0MZxg&#ovaWy6BkI z8|r`Ix9;^lNc-DcfFGaK54TR3^)4cGh`${%_1v5}?emZm>s#Lt&tK6{vS)koXzNJX z>B6R$M?KqnqwC1#&0)udNv@yyPJQ%be&y|Tm%lnZfBEc{D+>oF6R(aC4}Li1Ci=wW z&12+mF$GoqV-NR{1Eq>Vamluyph=ad+a`9+e(=|V$-Re$3l<3ld*xf3$K9>wU*Ad8 zpXpbY&*3A+*RC=9mY%Q{{<5Qc%d+IbbWf6_CqJ5A!GAV#;f$9|i!;spZ!|yOGi}xm z@tYkTMcbaUc#1_ohY9>@WoXWwAZwc z7)Mw>Yv?^*njXH{ZQD0*Vqd9YXYIgG4dVKleT%K$!K$^FUcIy1qPf!9raLq5!3P6> zh}L@BGF>IrnVFzxO?SQho$=iwb$8z+Lfiuy=NMlpxqhcFc4ySEe9VIC%NwJ6zrVYw zdfUkIhh$=I30HV@(N^pJ+6C;UePQ>X=cfX7*x*0=XuDaYQ`sBO4TYhcqsH{8i z(c9xX?{>CM@XlX(^pWfS5Vq~Q=K1eG|Mg1Yo+XcF4;(9E(ql_YPR5tn2fI|`8_!PZ zyH+ ssh-ed25519 /Gpyew CfIsypY8RtS9xiEz41Os6yTEJ0jLyq9abLnmD1BmIxo -kY6DRThZg1hsZsy5NtIwvronzqY+DntxMi/oJU2Lj1I --> piv-p256 ewCc3w Axo2RjgPlDAGnV1KDiFwrKyYeb0ScsjaQ0ayZqWEusHm -FSPAP9v5jXgaus25xR94woquDnz6CCPawXpzUxgLBEw --> piv-p256 6CL/Pw AxaFajLGlSPKOL3C13kdA5txo3XzaGyyJrEDeR5EGZFX -qNSby8foc3TUeMRkbLGEf/KhGMftfDdVs0yF/RJ0LBk --> ssh-ed25519 I2EdxQ pz+wkE+wVN4zQgM6zlOECWXzsGXNjhqEItmTGPKleBk -24kTeX0aj4LWrOlEyhKCd4vj3+d0Rr3xynC4yiS3E04 --> ssh-ed25519 J/iReg oYbqvVH3yyGrJHgruNtIDRlhqVyetK5o85RpxYR1NiQ -5k+78ZQsox92gUGw8JDHsK2dE09vMskLO5QDCAX4C2s --> ssh-ed25519 GNhSGw rVguQoCqPt7EcodF4+4QLkb3LZcfIRu7PqdhR2W/QgM -xTHsVt8uQldI+l+dWaOmLIkFAnkal6wlNwqsrG8JoHg --> ssh-ed25519 eXMAtA UcfI5tfsqs9wCacaTfH4U5E1kD7Mvk4kkruSbiwQngs -+mWFtbwsLW9fGGo4VKPc1JT2Cz66XBoVHNbunZyc2qQ --> ssh-ed25519 5hXocQ CrXLt0QWY5gKVYRpjoE2ipTkI99bOsz3e2RlHxdQlyY -aUrsUJgVtCCSyh49XXINzxTlCtFVD9vESoHSu2GK2oU ---- yw1hzyJgwgfb66dS4w4uuY9v4Dvtvjis/aURt3Uaa9c -0.z.`uF/sF7"RKDm 0ReB_?э+?eոG7Rv(d֎"F>>6IM0@ ^Ʀ -珴JBV`>{:jt[y6շt2I5 N8H֔N0ja@j?p}W \ No newline at end of file +-> ssh-ed25519 /Gpyew C+rKVFXo6MWUZBEEc4AdFF3Db807EGbvzHJP0tGRLRY +jnEN8TqZ8y5QxWbuaqvG+WpnVpAVHDm7Dozs4sGzOYY +-> piv-p256 ewCc3w A1U/YvdQECyfj4GCgQUByfC+4jIhZc/bkWjLWLw+T6LO +G7n3+xA62X5qlQ7KPH+//dDxn8si1gzV9tI75TFCPs4 +-> piv-p256 6CL/Pw A7l2SbFsaViVK56naWclm8aANxEcPkWTANbuMCWvX3I2 +NJhPqBCshDinyunqVexBUnhZgSU09ocVqACWYFFbf5g +-> ssh-ed25519 I2EdxQ HMZeIjU0RIgYoqsrK0yQ//+kefKc3kGELb3o/eJ4zCc +M9d5zHlD/XvNspW+YA8b2Pep9L4aJDXXGRqLB8tAiI8 +-> ssh-ed25519 J/iReg ZRBZU3zbfUAEm9w11rpQHFrqtlFT7wsv86Zls0R6bXc +zs/G9jGLrG5IE7G0DEuAgWTycEPiiRyu1o43svBQ3ZA +-> ssh-ed25519 GNhSGw 2JM3DCT6fblp+pLt5XVpmkZqAh4WWHu1/rcVwsQH728 +7uKO/m8H8qW6kJhMY0SVXpSwmCdBufDd/vHqntjCXs8 +-> ssh-ed25519 eXMAtA juIBoxU98Nl5JnNcBNNXlttpi7+kq2Kc9SBwytevgwI +JVsjwZt0MoSoaCPojSaY+JCzxsSdvnUliXHF9m/bs3c +-> ssh-ed25519 5hXocQ ZugAZW3AQWRZIKAxp013n4cO7wP6u4ZEEeFWSuWMvzA +Sf0V+eOJXKjiFMhddgU9P8U8iMtFtLw7s2yN8pw1EFM +--- kiL5m46S11eb5jgXmrVE2gK8G3UDZPdiy7P2HXAyUSM +'{}LSkYWE(1_[/bvPV58rnq-_ +˺T՚mIB3,m +GzZl5 4T|lS\al%m ѕpT f9F:<_vt>t}n?Y \ No newline at end of file diff --git a/secrets/neo/coturn_auth_secret.age b/secrets/neo/coturn_auth_secret.age index b0493c1e5cfb6dab654c2e88e19bf51950feeb22..5e08461e8d806ab0b952a945c818aad2be00c30d 100644 GIT binary patch literal 1079 zcmZ9KyYJgn07hX!A`(I!s6-cnfGG-ga_u-qD#)+IzOfTKi4(^#V8@T^*s&cui680! zLlFbeE=&j&iJ=k$LnWY92Z~TxIv_E?091@fh(|jxfDn&P<@XmHo$ouA@3!4|omJ^R zNaAOc%*T$MM*I0@o};$wbV;&9H?d(_xkZzA7gc|E0Fa#1dWvgq4T_f2N3v%135T{V zj;Mlz9t}mj!FHI=nCDs3wIof!Ov%sH9jCLCEw)`0b*=y#kG*^xdHR2}G&r`?F4=3k z1-Ax8lgR`mSdrj(L#aC(Act|gj5SUyjuNpb4HLP?j&lRc6N=QPoCJIH(HO3m+blX-b66(I$rYup;FMaE zD8RL*KbC!h(SuKbU@af(IFCv)q2pw*h2QiWL6pdQt5*} zPsRn_7*wuohprZNxuOX5WV{;z&g}T3O^%ZOutqJr0}zq8`MuBXFJw4IvtiMkPq%2Q zuwF<)d`@H(s+nBpLkf-dL=qQ(M-sq7in2JRQBa04HSldxc9_*JG7p~M2N3tHHPDKE zj>S8FO+-5b3HeDk*HD-})135=@|^cM&oN)RyD(w-R z8bzLArk&c9v2uMLA3}1Xay?bVwEeOq8qNwp-pZa0BwLZz>!0$NV95on_A)a>&@I!CcfE;OSelPoH?} z=HN{5%jxg0z51NkHpL{?29j>_eY@cje~?I#;|O$}ex+7H;`h z|9I@e+wsH4OYcozd;gWM-h40%oF||8@TEu8*S|*BZ?^B;J^%Ly@uMT@4G9<@UHj?x mZ%z?+UL5@S#mi5fyYk!ZkI(;g>GItRXMa3*(Kz$@^?v~I1#zzc literal 1079 zcmZY5$*bc80Dy5B@Ddz7>P5*xgdx~LPIJM= z00G-G^U`vTORmjKV$3!iv1(Inn4J<+b$3d}QE5!hF}Q4u zSvg#L-KkJ@g1D{1p}k-`-dT3#uB4oV0*KwFu)}eRw|XhJ`&7zKnKa*a&`8rfh_6>Q z8(Idj-9pkujfdk^5P}tipq;?vydgGQ*z~ocbNO|IwWgVKIIm-eF)_n-9<1&FaZemY zk=@k2Bw_GDwdGW!5YgmGlxkskIp6N#*Bis)HhAi3EIS=+Ch z868mxCe6A!@6y?#U)Z5ws0ScP7`4^WnHl@WMwrm3t^^xRbfwhYKoEp-HOg6GYjlkM3Nohe;t?v?DNbD5?UDc-oR#0Cl|gvL%KukA=%v z6Kd4Yk(sof6@Jy*lD1*Jd4Tc=s@K%THas#*PX#@zsupymx)?fEFX7cr zAPjlSxDNtd&*n&AHd;}+XasAN6nr}{0|C=|$x?~N&6hEhOc<~B5t3837%&(NKnSG^ zVUkb$aFwx1vUF-RBU`iQVu2*QC~^7$B@04!^th)f->m`G7wA=<=r- VH~1TV>-^^{Z$A0@+ujTJ{sDw4Zr=a^ diff --git a/secrets/neo/database_extra_config.age b/secrets/neo/database_extra_config.age index 604be952f29b460e83cb0d3913fdf3c0a6a5f86f..1e487f1fbc4c59e3cec543ad6f497f335cb24dff 100644 GIT binary patch literal 1187 zcmZY7+soSo7zS`fnKR5y*s+fp9u3pX<_Ll9A*60o|FMXA2kPIp@IfNY?Obdl<9s&1YkE=xT~Hfnb=x&3xta-8-+? zLTGb20Qe$(+PZ-^tYuy`b;x#_$u7 z$-#2ZhUR859b)l>w;CK)ZUipU45_F(Y^Ni2G=)?qZBY6$1nLMh^oW3E3C1Jpi_R#t zM{~pP-G1T(E*g3kHvJHae6NSysaa~5N-2=d#EM{-XK+lhvLKhkIf>XA%4w{K=pAkz z2Sg34@@(jO08vi+VXSM4)vH==VFyyv;DW*JB+Yt3wId6Y$)W6{y8O>Z{=I&DqK3u* zR46Bb5+9N<&!}jbsb-Mg(wgwh>qXieZr6EV?U`Lcr41lZkg33;m_a(}C^E%jB9S?! z%z3{;A^x76o`eZhs3D28fKsUh%B0&QDNKUZGM8CuZrgTGPMbBaV_-7gkZ8|GV}(x> zUIMO?H|~Empf`B?uDyBq?M?3YvmLuW=J!eZ`nGR&p6>k6z3WA0`$zY#Jq-EU^QU(I z))m&rTXx6p(Ty9q56^Ujvw8+ z_N=hWI``Uv`iHkRUp%Nny}zlDPlXxI^5jLX)4nm3IL z9*kl+BdKdhlEX`Gz%>jB0S6;?PT@R6YhBizMX3*)ww{h{r#hZSB?IPcpd6>BUblKy zY7aoAH&z(E+sHRF*-`RRFj9h{l*uK?VSV<07FK7#&XyHPYSGI;)kGQIBvVq(kBu1& z`?9Mc83s{y0L~r4X-rTCAo>HUf!isL;-cU1p;8T_RZLh$tfrgx6lBDwL`Moos1t6# zNWp}j3c1k)lYK@CG(?&*tmW8+=|NxU6~hFs3+;MV3)x{U z1S5%=!ySIWlWa;r8n6;}ovs{IF>z{wa=QWP`M8NRd?Re@%Yq+rs*>^@8bWKtc8%oB z9+~8Lxkqv_$(7)lnDKoQp5Zp@`+=OUU|60JbExL4HZe1dY4>uK9H)RbIJ$hoA_ z79!Ih6RsDO{bnH8BGD=%COs_Y?Q9?-q{dH;vPf9KwC$FtzS5aNoYUf{IImfHMa4$3 z)SMe3(?igjgLY85KBRy`p^$4v4g!0&lX`~SR2gnIux+{u+D=9pA~mXjR2*sf5}l3+ z;Kc9m%9jpaoc{LLg;4A{+?Dk`R~L?* zc<~|W{K}~-@U0g{xBj_(NgSvqJ6vcwzIl@dofOYSN@a diff --git a/secrets/neo/ldap_synapse_password.age b/secrets/neo/ldap_synapse_password.age index 0aba78b..9976942 100644 --- a/secrets/neo/ldap_synapse_password.age +++ b/secrets/neo/ldap_synapse_password.age @@ -1,20 +1,19 @@ age-encryption.org/v1 --> ssh-ed25519 /Gpyew oaVD79l3EZWfSVKb8XpqWAV8NKXySVAPbWLoT1UA52A -+kOAxHr3zaV5i0JpQAtlAdU95Q9M3rJqGtIJ8XvPbkQ --> piv-p256 ewCc3w AivgKvbuHgMuIJkXqo2/Cp3IF5MJAbfxKBMngMbKvQq/ -OKe5ZZH1BcR02enuqgNYQR1xUk4nwHnHUwFeWNa1+Eg --> piv-p256 6CL/Pw A9Tk3dUEE9IL2Mke3E3mOe19oqDS9YTDZK3yRV75eJX4 -8qCQHjVWgfLk99WfT1694g3DvCozGbfYrf/cvsWygGU --> ssh-ed25519 I2EdxQ iD7bG+gD5EB6IKt37N5wBIK+gykxKX08nBJmqUMIKyE -xs5EhKazMdwtYiBh8DWyZfp9N6oHUXKAUwJ3ipGnoUo --> ssh-ed25519 J/iReg z+J9LYzBpAmrk+qs/bKM+dWZADzaCG1Zn2++aqngiUI -ZKj2uEEtSxI+VZmFMTIs/YCN27Dzaez55OHcRRcXGGg --> ssh-ed25519 GNhSGw QiKR6ruzN9obAMMWEX1SJP6cuWG+zPer1EOEOubWcyQ -EGxT2dlZg9SBCH7MI6HygE6bgeyM2Njj+bfc9HVHAHU --> ssh-ed25519 eXMAtA iD5onNylX7xPzgCZDnbio6+5GtbuO9lXDE+mwVb/jlU -rdadtpwMGEAwZOhYId9xeryALddEK3T4DQP6dfgSYN4 --> ssh-ed25519 5hXocQ bhNzIK/vKeNNpqhZA9dEtHOlfYQ4sZpwF4Xy2Xm+yA4 -pD1xgl7iR1nYEjt7TcMQC2WzBlaMukoMNwBgomm0zzo ---- tMEzkXbsknws8FNrhwbH30AMAvDUtmI+IiQwUYCTLfs -׿[Gl -"7BMػr6X5Md D2sYǪ vE[p \ No newline at end of file +-> ssh-ed25519 /Gpyew dGSuawZ0RvN2N2ihmoc3qY274uEgOs9HvWDUcl9kOhM +pFZJTtOS6RGW95yC+9CExu/VI88rXlG2UGsEWrTDV3U +-> piv-p256 ewCc3w A91zP1PGWv5pkh4jfKZ1brb1VR7tZhwVh9Pms5KOZLAx +xC84LGbuTiO/UnC3ZAMI63Gv3XhQbNJ8sjP9DoCwVo8 +-> piv-p256 6CL/Pw AoSgTWZwGO7uPKE2+PPuDrovzeXH5xykqpUeFxZeUx8O +ZcrrjDJkXmlm9hBvms0oXPHe292DqN4LM1Ne9GkJzUA +-> ssh-ed25519 I2EdxQ byJwWDmWt+m8rEg85g+UQIjewKsrRpCeMYdcS7scvBM +tFOBJfUSSQ/p2jd2YeiUPSJxnRxw4t/rBtB0QPQHSiU +-> ssh-ed25519 J/iReg AKIEv7rUeNmMp23OeHO5FMAMjfN+ZyS7RPh/d/tz+Vo +IxVtVApK+K9G2Zn83QSkQSiW813lPR/i631Sk3uBNB4 +-> ssh-ed25519 GNhSGw fOuHlzZ+yJcQGgEcY7gpmQDmJK0Vu1HcNgaLZHs9riA +6zG8JsaWPjqYoPb9dHIdnc423vLEjhWlUS5MXpOjh3o +-> ssh-ed25519 eXMAtA GNNPHWjuGcvptxVlqXd+0wC3Yze2xGMIYiTKCP0Y20I +lKvkvCbLGxWMr1Ad20bOdgxSFK2Dzz8hYaoqzFmOcI8 +-> ssh-ed25519 5hXocQ 2curLeRtRWLh3M4rjBKGpOxeQ49IKOVMYHgM453iLm4 +Rc/IZ48ZsICzrDFE7GMrA2sCCwOpW8l2ITKdHteQ6mQ +--- +2GDVm6ZCtBJxooh5Kbx0BABvO+lpphyU5Iw6o3FuDI +2 JLJk*uѱZx.lJGB'.?ya" \ No newline at end of file diff --git a/secrets/neo/note_oidc_extra_config.age b/secrets/neo/note_oidc_extra_config.age index 4e0abde1eaf296202dfdcfaaeb7c2e4842e294ed..5fa5f0578b032b057b31acdf6cb232da13d2c36a 100644 GIT binary patch literal 1555 zcmZY5`)?Bk0Kjpcixd0fZ5egyD?HAP7MsLwRP(TgDrd$&erf1EPpOihsa2pX3{588wrk^ObCo z7c;ZOJPSw~g?f&%P~g-|9MbDGCMDp^Rv1~CFMIP&rj&(JRzjajMqMH;g#|Y6mn;yM zFA)jY7K0(lT?mQ_XO$I@w8K}ln_{FfCuZV!oT7Of0Oa%!1&7zJnR#uuh1C=wm6HL;TGgPB-Q@ChQMUpDm;sQ}burnB%G0>L0#tl&lhYojU z?0F(3=xkbww;BU_mk$TAs)YrE)*`OcR?z~C=$sPxAIo5M1Kx)g>&n;?X$lR~21ymP zsjLWF_F-%_4HU&3K%taiRtcm_f{^C0dCSm`q1G6Au0i3Tkt5(;Lqq|ufxOFXT> z;(iNYGQnmj;Nmnn0M3U*z!-@5$cjJ^j!1+h3IMK&M?m^f4Fh;T!zx+3m3F}96joq; zl^mJjL0_qwDf2c{LDKn+8Jw&n0&rexP6idlkRRsA3P$-$U^zxZDE>JRYDzt2HAbzuCcPHd#6CsoVmXXVD1Sq2c#%8tU z_^^VrQAyd8QF*N~LPQ{^5V6MafYzYGkyu!w@QO!6RbVNGQvW;2P`J5hR%S6UCb}3V zA9oh?9;cC16+%IS3}xL8L~o4x?CuOF__Ia51ynSZeRS}|Z1Z3QSnMI6b2 zF*)vllTOiXCX9|~{C}FBqeR-Lgd|%u1L7eW#o`st=}8!!B$DG;f1b7w80*Y~DqId4 z$!fEp;O%VH$m2mCD)6+Um`AFhms0VhBZX94C?AO!d@R;nY?eu%LakOSGi2G|MPt!K z(##4)5SFwO1>1sNK&#^I0WoS8jXD%WMTyWUhP{c7aG##@@wkcnw=I*-&40S74Zb&} zuIXBKapzV69QB2_@zkk(e?=CI{$s}N%OQ^yX5+#&bC-OzWYx;_ z2U7-zg}P@Qd+y-EBZuy7wTx?-Dz_Du?fCQQbF1dDYa7VBjh|6h#@BA%ymxNRKy}*s zqv>Vs+wWxuS|;v(z2`nSZnDV#+}=2}wP|XwK3mtHocVolOBz2pg}J_=M*T$mqX&!J zsr$qA^-J4cVUE|`=;#`?e(zJdmG7XNl7PF{)q}p8`0%RJ+x;5f>Fa1afBl28ts95z zJM`xXkLbC0@@Q%WaIt^d;tQFiV#G#afpzt^5%VUxm;YVAw4B4;dXpFiG{q9vZt`*$?+#NJG&->;s$KQGTH*oK}bW6?Z)_ucojPhbjOZ|rB z?`ek2=qpv~8wO)F-Kk#*IzSF?YMB&&rx*VE7u{O}-qF_G?|;;{y7x}U-9<0N+oTQC zk8Qa!YvW1rQoP9@+}~aMpeuN6sr{LY!^iy8dd)JDfB%P+?`j}CyZ84eW+nSB4Gi0L z_;$z`+}Fup-u20tt3wtX-F^`2IDGv1vnz+q95>K;aq^e*2lvotCG_t0`6s4rJrG)W s=XBs~a;Wn1Cqa7KXG*mZoi*KGG)w zGCZHW1m@wXaL9NF9-BNoRA3wrl{X9&xG_LMUc-SXr>7@~KZ<|B=kEIr34Ar5VAI8< zBFc&9<+K3gKvi{}I+KZ1^Be@jpivE2lSMwSwn93m28K*oFREu6olZQXH!y}k0^xwH zfwY*dJfs5&w`sDwBjZ~=-`BYV77t&ip)kR-h(%tNPXJxvPkqQ>oj>`?;K{;MU$ zTy+wH5jCGTu{C)$iqIGk(1-M9g+TSD6e2cIC5_gBdx~Zt>aj2`qeqhLNi7D+s^+`` zCnY=`p=>&nq$31v9!mg7M8y0E8}j246-Ox};6}s$wGfj7a6PbKh4#kaC}x3^;UX9> z<$@F@H0BsDpP(ti=n)#cu@o58G6_J1B80>uIBVvKLZTEj>4WjSL5}Aoq?xcA(k>YB za)l6?%S3DsSF}MmS8%Ik4`V=}7-;h9=yW`0X!K$l#!3q*ip{z4ybtv_=$zn(?U11I zvK}EpX52Eh~N(RFL%qY6WDWfzsLc8Q9b(+!!d^?w>1@yo1R?FSP&#@HCJ;|87H<z!E!S2ZJH7AnM27R;^@ogSQv+AQ&8+Ns2KNCB2?IY$X(OK9E|e(d(L#cLt{MEUJi4C)-7R!ELwPf5+{p(wQ zxLtX_ZNR?C&wTRgrNwu*ym{%5@`0~(2%F`OC-R1^%ig-xdO`Jd#XD%3#U86$InHa%JU$Sxh$^>YPuwQks+ zFOT1u{Kkbr_j_&7+kNl!%^e+ISq9uKlgT58-FpMe`JRQ$=ECKU-+G!LmcPmP`9 z?p|HnyR&L^clqkaw9}s4xA&szgP^dt;y`vrX#3)7`sNYi1n#fOGxKK+mL`NY{VV<5r%voRut>fMUYd5Et(sxI(Rt-$ z)r*s+bm>~#dQ$D!{-f{y`1?V3>d(xYs>{1qv(cw(y=~78u@P?#EU(&k#@uqSGkO|7 zrVvf*m(K}r*CyYq*fej~2lH<192u*8{eo&==Pv5m^FyysoU`YP+mndzqoyIJ>z8a> rBvdYSgB!SBvhL>AY2>%lK3eMw9o=7d`zjG4`>ziU?VH-oETsPdcAr$d diff --git a/secrets/restic/apprentix/base-password.age b/secrets/restic/apprentix/base-password.age index 10ca63dcefc75fa414bc29619f3708b5738e2cd4..5eaaf2a59e0c07768c723704759a38c315124752 100644 GIT binary patch literal 1235 zcmZY5+pp6E0Dy5lfsoATgQEBVW&tF@GPbts)<#6yt=F|Z>$=^#I z+qd1$QrB<9127U>9_{t4m|24m1jM3-E|PR3%1B(hN#=keX*pO;=9xgU)TS^%^vtB# zYlbP$=6G<(S7;;RBy>?RB_LjG<4%JBpq^cDCK*E&CuL2`cRk+j8d=&R%J_d;*A8M` z2uVfFVa9-mQQTz!uj=K*L@QTygFH2W)83F`^(09Q_*!OAZVDBg1&A`EM=U}y7@guI zFr@V8j_o2S_8&^!1VEg!OTY&OplltW<`N=Wkd@zgaVU3#dS2mrVj9;?nt& zT*HP1)wSz1j3i8Q>~-o{PKk{8lHp@%OVk88BX`p}m#|s4OvqrLuHXPDkiOH+XsXD& zH(z8SvNn>Ukmd|)%~aJZxKP%jouEnesu{UsSa}0zTTC`6I{j8FS=WRJfe1jcL?!WF zS}0a&0Hzrr;5^tbDnl4kyH2`N&uBrbT~6GradEp~Hli7yfC1IV6(+^IdIM`ndf2o9 z-5#qcAB7tP97knWS2ZOWsj{6u3yQ4(&8Mgi2W5$Tp34>`UBm}OQ6AD2%p!<#Q(|vv zqeV-mhtVWbVL)6(9cUzCDpbwoiom#(7t#ojP`L_PD0ItR8}_q9CBmBl)R0C6j%I=| zol-&C?CF>kFtnZ=>0Yv96o@KMv&iHvnpqO?J|0bxva1R*^xOV2PMLR(zZCH zTSVdwgyl=H!4(EsJR;ku#TL>^Q#5W2>zi&3>5P?Du@0)bq-!>aHafMC@i>^g`6ObM zTtkZH1FD5}>Mb$|;xriI8my`zqJ-YViw^tmT8?0G! ztaA4Kqc5Eu9seS;X6LF2d}96f5AM19z>;~tFxuhst9PUE6PtI@r=C4=b0LO7mJmErcC{7aOap?~0ad+pkyYrAZJF`18yE}WEo!yxcwB5PyoY}eT z?A$7g=piI5yI3GX22vJTA{O{i9ju2gb~v1uQ>KY{DGEt@Ftjl1sq8QK@cr@YtMZ&Y z65Yu5bZfN9a@Dk-P0Vdhj>p=ZECDc_MUtXWFA-{zm4ptR7KM&h=Qu?c#tPeLlTxV< zG}(ro!>rIUGV_rLl+^@ixI>xZ`m{&{lN_V4^M)0L{dBHTgPV{eMvT^oWF^aC>ff5J z`*St`=aO=OiC~aKO^amPW(KO2UBS0a!4>N{2C3FD1a`AZjj;Lr2(JXqjxbCBJQrJi zy-kcv3BZYg-2-Z&)9hC>92TQMqbDF0#`i#&AOFY7VYPJgrWF??+1JcmNy55;S}nm~ zwm{*NB5ikEvFwE!2#3XvG4K#6k%zp=xIQpUDy48O+^4F%&&ESMqBTn`uuMfOIyI)) z2Wy2}SEPWF6tMq^`3%<*xn71u3N=SW=LQw3%0xp5RQ9O+9Woxn!jdELSQ%<(%6(xCFa42TZArbDNDW7HWc6pV0GuQV8n zw`xRPV~8LL;9~4&iB89iI38z;Lm9&nBPBUH!0|n25~Y*~d=bvr#{LdCknL>t;Rs+Weo!cRT88J-)op_>MOs!*Lds0{KC=ef9!wxd2Ri+UDtoc_w=H@;4>ei zmkwV^o&S;EWK4A)xd9#B_cY)f9(lWdIWx2ShX?z=o?fJc`>!8Z*v7tl=hoh}g$pNv znZ{2iKD+IMBj!(h_v)|@Fzi)HAaNwy|?l8jlcb?dJ zhB%S7o;$wvtGiBJrmU&2m#?ncaB ssh-ed25519 cZNEGg bcq2jdSV1iE6alm6V4KPX9MJUGF1MwIKVczTCKp0VGw -+MCRVeS46FKPHMH6VeSQ1P9aCU6+LmYtTlgdnHP8TUw --> piv-p256 ewCc3w A34SQY8XltI0bXv5WNFztuk5eBbUiT2Vqmue4xRUTn1D -r76a0kI7G68qf7onVGAq4S5Z32DR6BEmCrSUZ+oYg8A --> piv-p256 6CL/Pw Am45lx4DKBrRYWaDQA6F+5aN83+RTyPOuls06IuN0wR7 -39yE14NK9KhezDSFADfvIIzFoxEgUDV6REtb4ztpS1s --> ssh-ed25519 I2EdxQ TYjJ2+ItmyRRzJLeQxNsyEtDy3GKsE7+X2EtqhEDmWI -6D3WNy7XUrRphd4qSeCJpgxIvuUsaO5Ip2geK22DnbI --> ssh-ed25519 J/iReg lmLKh/Sl9ZCMmLsfsh7jx9GdUbB49w/zrYtSM4YfaGE -tOtKJkQrqI/xgVfLf64FCjsnJTxjj5YuXk1EmjXD79E --> ssh-ed25519 GNhSGw UnFhELQY7g4PgkSJNXEFHIeeKwlW9NiUQmrQTY4KuCw -F7buN8iYpM0CkswV+O/jyMG73SjD6hY+AjULp7t1WCI --> ssh-ed25519 eXMAtA 2n2v9JWA4s7b91DyfaYau/cCx06JgNKeqlBXquSJYUA -ToeRSuVsb9pLmZQxYKTxIEF/i3XTZDAM6MqBuEidClY --> ssh-ed25519 5hXocQ s1XTnL4QkBRhW9SRQt0KrOj6gQRhfZm139UYGe7t2TM -tT5EdRyoilgWlZ8X6qfEB1Fe6GQ1f8V4gFvwFweal2E ---- bjfHy+S+lcKqOAt/hnbXDtlbDz02YuRsce6XM4KMwXg -HeL[1qrr`趘RIp) 8^Av_6xy+.d3d38Y%̡=2Ce|p%>[tW9]8Q9i#3T \ No newline at end of file +-> ssh-ed25519 cZNEGg b505duRCUjy7CzrtgQ/69TrJjlG6zbJbuW0tyQZO+l4 +jGGys+6yoFd3COQaSqVSta/82Y0ueVohoi6IO1dZ48M +-> piv-p256 ewCc3w Av/PLEFUUWaPKb29Kxw9BHYEHfNyr+bgrQXhjW7W2ATE +7awUEWXX+YThjPWiPUTQRbNx2IySdLeaPUbbH6U7iKU +-> piv-p256 6CL/Pw A5QaKFv5GZJti1UTgX4LVWfHEWbM+jSlZrVK++1aW3Ag +3fLlT0f14jg4Q31Ar5taxqwpK6kDUwyZBNwgnx7N7zI +-> ssh-ed25519 I2EdxQ VUG0TCvv1BdYrMYRS70W0w4tyH97TL0E2J8VFcyXXXQ +DVLF5ndD93np5SbcuawHmQRiUJFglVno5nr2DeFnmJ0 +-> ssh-ed25519 J/iReg gUeVDoBNUTTxJxZWdwX3zseuzyQUDoDbDsXZhL68TjI +qvRlMRBco+YtggijnRfMu/6WAVfVSZpmy2DMyc7Knxg +-> ssh-ed25519 GNhSGw iVmUqoMBfO8Rnod3gDTv+R9gIxcEvAH9Q2bPBsN3sFU +uCz6NYMtBIyTWu6pLZppCESnC+7scVr3augdMHpi67I +-> ssh-ed25519 eXMAtA KvTKKRaHmzRX8JqfDNh+NiQ/NWDZkrDolzUw7V6X8Bw +2dWyACxhfz/dRe30dnA1ee6vIn3zqat1wVrQmuiE0MY +-> ssh-ed25519 5hXocQ bth9w7W2pI+mzHZHdE085CwxYfQ01hkXqPjdNEyuWxg +rYt52wmImpd53hvad2aykOv8FI0pFuyCWB+00fkeF/0 +--- sYrrk7yB4pUvvfwri3jsseXhQ8+88uMhxv6REVqRV8c + Dz| 7Ye++WqyQn-Z +wyhA712JB6*=Ci3%,ExBZ;}Mp\80?y:X;frj%(M2x܎d*$ \ No newline at end of file diff --git a/secrets/restic/client_env.age b/secrets/restic/client_env.age index 77d12cb098818fe94957928fd308135c8c2e3ed5..436380ca48b16b424d7bc884aee09e21d723278c 100644 GIT binary patch literal 2185 zcmZXU%ggKr8OGfS5d;y`qTp;=I1Hbe+$IT1F`3*mNhX=xCLC&Wzh5VlOvVa=9C0ZK z76dscSi$}SiU+D-H(j{zLg}K?g@V#u7cG7+{l358$M1Qb_x02GE^fkM>BhWmzSRyX zI6=VOtE=-lyNe?j!ys}6mzb)hD^0u!vuT9Z5?KlFXH_;)JULctTs-=PUNREebVDjJ zH3d+z(GU@u_s)6m9=3Xts~Bp@wIyca?Pv}2l4GHB;*(4Ikd~Vf9#bo*KqIqa{e?~? zGSNd?KexWX4Jc;!#|zssO6yq&56jmLuszX?K?ExAU6r}e9iCYT=Q;%OI*^JgIkN5U zu%(P>Kb8+^8FqGbYUL~^wvYvZZNbT`EUuXAa^hsw8x>xjWK|7kvhG~GEoPvgiF4R! zn7i3hK}sHKQ`pwa#Wr)*K+4>Sw%iUj{nO5BAJQg|u+w=Zlj^3Osa=c=#Ry`D&`KrQ zP*K7nY+I5xwXb1kxo1Q{1n8bLSSMKHJr>i9X3X2jKxUdbIKkE-v%#gKIvbsFRKtff zknfgwUNzbo+}d5%@LSFj7M9$S;@ph{uB2*^w-ptmXa!aH!cagyv>e7(oC&(L9?cDY zkqD@CB+>{9jtmGzYO15B=998{NaIG4?Cw`0DH%~LG+4sfE;J?Bu5S5J%LA^s^-!@O z#yRKqmYvZXU}291Sq?dj!Q-YePcR;pK3EJ0i#j$vj_htYS*qcx!sFh|s+Opg6&AFh zWWHzjBOL`Z1s48-B;7!CFiMdNBcGrEUUtd#1eEFVD#P?}T_>1wIv(*_^@@@Z6=tX) zW?fXLODAUX(kJ3WH+~n|F11Q}G(i)AbD-8TPTe_GplROcNtUWQ+O+Z zWqV((;aALg4oAiB{L#MM1O&LD+&2gam2wMM((5~>oX12@9I*p@3|t+6u%j0Ge?@X^mP#M1W?!&p0n-quzryYw5Osv=XzwYWsQRC3fWm9?Itw*R62?} z8JIdbaikuzK8n-ax3UU2v9w0oGB)o8hF(u{9(H<PN_ z=&G-}&c&j7;bp_=mrgSb*`_LnA%_88Unrg*Oi$FaI0vOiN7^kl4 z5)8M~Wn4~x#pt&)kC`KcIFc5-8{{acV>Z}g=OJ{voA8ltY3US~kN!WeW=H-$N~DD{ znfH=VSsDYKPmYzX)guWL)pYS9(f36iab2RP2;iXN2(vk0VNMslW9yisYge2cG}T8U6M!?NhW2H5<4{PcIfef!mS z+~Tz_E9&pv5C8b`50;Pq`ue-mm&SJ@?EUNKzw*oJY4f=cfBo9ix4!!1hhK+Z={`w* z_HOy!%YXjf{LS4ro`3&O-+A%bv;H@~;-+u^<=KmWzW>uN{OkG8UpzU#_qAVq@e}l= vpS<+r&sN~Suj1R!|MMpI#&_S+-~6c2U&%kP|Mu+ZAAa)tGP~+NhXtfGTnT+Osh{bZitrR~kEav$Tv)jS*E;hHTToSU4J#_&3++{56Cc4-{+qze!}$jB2h zyX#v`9AvsV0i$u*--n=I$q}xGeWhop%jA@W6lt?dnNajF2r>4;?!>H`2-*v{hgVSD zxSF7FgfGM8%uIensKgdCCW#$FZQahrt(Mwlr7*x%as(A_cX@H>mSVV5$8LdmJOEKea?MM|lv`6hB7^m&=U%oWNb@~KH~xX|?( zKv9o_K`uLPE{|rYi*$}lL3p&VfVh(Vwo-#=6f7fR7izVo3fyjJJ>+Cj;{8d@Kt%V~ z+Q#Kj+u_HCORb0r`i_@xv#E?M!3+?pg9cvZC2(B4%bXnnQn6<;tQE5XtO;^PIlwK@}SBKp56__A!=f~?ySJ*iA-)v1b-8BeK0P)O)=mS-b$MY6OTM68l zY<(XUr%^k>4IQmDeHV(LvG}^d`a^dXUOIDRMTF2Pn6*fC86VT@@th zNrhHi9N*pb3)N^B$|1E{ypEIaHIf>-RDZ#biBsS1Oh$z0VgTpEKqH0cth@EmFXe}3 zw>F$f*?QZgM>XIoJcWqXV|7$pP56MG(v~fwJ?9$=N?8IG;pXg+3+J7^xc4!0MjPk; zVBBg7l$r4fFrgwZYt5$)n-Yn=hf`FOvV&Ihq_<#=4ux&wRs;GxBCiEwYL3m^YMWhZZDxQFQzzexov}mEa*Or3-SYt@(Q6`w zI-jXs@hY7C3yRFzsy_FEz*}^16{)6db~R^(D43~iC35*iO`vQ$)M_8X%~Q>eeT^9y zU|j|5gq)}VVX-t8^&F-pMllt*EXQVZnPH;9yW->o!g_VyA?$G7Z;5%+>UE|#{-RyD zHaF#+G!5{PkjaY7_9Na1x7qO2uR{aCiIGVKqyVF8Pv5(~Dqov~$6Y4o=q^c+BrvYZ zHJdpJTSX_zw4x-mjyhY7evm>1;R&uM>h7gSt3rGD2cDC_;Pjx~-2vxfETo}-c!&gT z`v?sxd<~NKxsiL*aEQcCP#qQ2Sp+gxF9UCX>7%dn|9XD<@@Jm?`@8JVzx%HL*$=$% z(vx4m_w4)q7vA@gSHJbSe?}kvQ1IGUK7m~IAAawTKl$d@UU~w({ndZ``_bc{y1)JN z%hjV#vv0lkNY0-7%U@r7^2%fAjgNi)m0x{>|M+jd{rabV_KP=u^o6fKc?Wzh`@yTv myakQ#e6jiEC*S0LO92irNh8!6+fzQUaN_*~{GaAz^mT?#|5a>|FLTyGurMpV`^nnc3O7 z?1oC`BxMK(qU@q{IiR3miKv+RV3;8lW<_Rs@=``v1W`R06lFct`3pYq{lQN%w3Ief zMiX~zIm1VskpYI8L~4D~_sx{1W??v!Pg)HX)$n8h2qKKaO16xJ5gW*=-*Z^c8Gszo zl1)F0HVKHA)lSn^61A~JDh0akv#6FMkx{KdRUFzyX|hbqywI{p49OE+4yK#`Xs#8e z+$@YFwTMu1Q4$Y|t_hHnMxH(|15;4g4_)Y2%M1%=3u$27qeI=6stWz zQ$km7R!oUbw*#%``GS$wcqGwHXWbIXGN{V71+y~M$4G^fsp`2IA-{o}4jjEuPE>>rd0Vw)@W=u_N2gDdG%3&8(NBB%^YQk?l=5)~g0nT`x7r5;JIGcHB=F1EeXMOm3*cV?To>%Dtu}@eMOy z8(Ub&P-+K-G{KXLO3Zf=kE>JtoQpz3+VYobTARVgc#_HGkTC12^G>CGbSnV zeO*>eb3k=fDoqV7mY#}GVoWlF9@n>GC9)=n;>2{At41bou#+Lc6}>l5X{a*w z5)4j?6;q1_%}(OthsVmxwyo%Ug}KwZ`9*}Rhd;?z5(HS=%IZuoh}&P%_LcfWtrpWn?qv+)AATb%!L0ouD|@8zE! z!cO0I_}vk}6UqA0Z78mi4 zzVh~MUk;cnZr%9Qbz^qDDW{)%H8^oyx-x&T)nB{t!>)ajH@jK9tGWD^GsLkyJUhR? IdaknTZ)lspMgRZ+ literal 1235 zcmZ9~+pp6E90za{5{V;n;z^LigED+ktcPCKb*&n-m#u5JcI|FmM?`kIc5TnvuHB{G z0x=vCG@NK6A$S2bkq5$qArL`Hyzu~s@IXYohDcCjI3OYdCa4MV=#!qm;Pd_de&0q* zpVVC~7drJRC? z7DV%ou=t7`l5R$F97-`H0qDHThC!0(U=NRAB!*n$25~1toDV{~^AOyvNA(!J- zFIkkth*4ChU*fQYT5dZEP_uM0khK~MhQ8_hIp#Yvsu&-*~rXz zA_+HInpuogSkJpmL|9xQl4F4(<(xKylBzG;aL^nWRanRbby4>aA7Qb6PjMrWLF7bw zakr#d^E5f09oa#Lkpq`<88RcZ2Z}%H59wAUF8pnBGH$VCm`$iApcOma3Q0|2=BQEuEI|fb zAE5JIT`&7NwwOVLSXr-py8$et{pOw>?}G2GUbDShxUuf)@omdv!W+x+`g0F?0;r%#f~2jQ?+B#?97)-h0Xefx!0cBeCxRI z-NjOB<)`b6pdcRw(5=)idF^rhWY2X$F5%6^p$*07(epyi!Z-*-<|u@o9ycw-@N?n z?BNR^J^j)KHOJpPIQIP957Fn3Ui;+G;kB>ad~ntK>Bo1homU%c&htyB-rhH}dUn;` zeYeYJ?@EPvWPj8jySaUS#oE^|{dD4w+keepda{4|yrypbtg&_dpSf_;u8HI+d9dZ~ NxrdLw^ZN%Y{|43kzHa~k diff --git a/secrets/restic/jitsi/base-repo.age b/secrets/restic/jitsi/base-repo.age index 61d0ece..ba6e049 100644 --- a/secrets/restic/jitsi/base-repo.age +++ b/secrets/restic/jitsi/base-repo.age @@ -1,19 +1,21 @@ age-encryption.org/v1 --> ssh-ed25519 iTd7eA WseG1+SqvezgyoBLrgak/0tSvvbQQ+QIJqZyCPUggzo -Bh55tP5c3HTswx87AoYgObE1PAresp9lfJaWtIm03fI --> piv-p256 ewCc3w A6lCxXXrzlGIO0Qx1Fc9uQzO9b2+xZx91KFCBrC5fnxD -2ID5qSJX6sZTbKI57Ao5dVF9il8Xhs0PiVk8eSQ35Sg --> piv-p256 6CL/Pw AyKjnMCiVQ5XWJ+RQfu7lLxq4m8bwLdiRmofFCBjdnxY -iyuKo9p9pBuq+aYVxOwIFImTUJwpli09A6OiU+vQsH4 --> ssh-ed25519 I2EdxQ eU1NDqc9CEn8NpSsQHlw9UY3o/Et+T2q37THq3TeHRU -vmaNfgxmmJNTeFApFAjak2hGrQWWUKa+wajkBgS4z74 --> ssh-ed25519 J/iReg ipTx8RW/K9Ai2/qonG3G3sd+00A50CFYa1WjRY0EiGo -n/zGjEICesORNjRIqChXdGa9UnP47KsMwfU6tiRMaaM --> ssh-ed25519 GNhSGw Is8GR7vI/M3raf9eX4rnViRdWbnufy7e+1hofdx68Ds -nkCoEH+pNDFgi+lsns4sfS50o88AgcFobgzQoyLgaxU --> ssh-ed25519 eXMAtA jwhasU+1JyJHTZDwd7NyM5EE9kW0DJBdqtYx8pReN30 -S5CHvyxHXsbyQmehSjVa37Bz/xHPU/nQXiuCsbqmDao --> ssh-ed25519 5hXocQ DJhIB+JMelQjtXuh/fbN5UtzxXQm4T5aPWf29NNEd2A -wY4QrgVJSkMi59CRzAuAe1I5YuVWIh5XS0v0IWB4I8c ---- WtoelPBHgCE2zfHo91uvElSB9HhLOk6Oneza66wxPUo -4GڵՅ3KAZ6=Lg% u6>>oܕW]BW2s~ їuI(I2d7DiSmٹ*6,@G)iEi eA \ No newline at end of file +-> ssh-ed25519 iTd7eA k4i0XcuTEh89RSq4BM4qUFXMxjWSU+fLT1Ed+73nR04 +F3APsOLs+YBfusICWDOsBfe2WszRe4+T/jCLfQSWQnM +-> piv-p256 ewCc3w A77wfia7Qc0AuHgIemTdKutvHsXx8tObmE7yvbzsNsPn +IuERUZXYmiNZ5zkwy8cM17BJuJD56XwLRrKxsjWp4Sc +-> piv-p256 6CL/Pw A8dB4yOVtiHnHyVtq7rscNvfL8QZei+gidh9SesoZ3Ya +jLNmkCjdKLcRl2Pe952lfUzTni+6z8hthyCJITjthlI +-> ssh-ed25519 I2EdxQ ekqq4H5HU6YF50vpobbRYfjeazFEJ8w7MciDTttdHVo +/ODzPwFUjld8ERnoqgbfvrTEar+GNA/dqEzkQiB5du8 +-> ssh-ed25519 J/iReg cBzXDD7BhsYKPx+56rKVkgdkQ47LWNtHTzwy0kNEQA4 +HB4FuqFfUF0ZfwnhiTemz7abay8Dk/wyEoXgPI45O7U +-> ssh-ed25519 GNhSGw QejdXlZ5ybhfzMV3LyJ0qKQl/IzUOhOqUbOWq63k5Cc +VuLHlvaq0C8BwjUVFsWBTOmYJwa9T2K9jUpstanNU4w +-> ssh-ed25519 eXMAtA cvCYJLA5+jxcydHVxWR7/T22sL8o1fRfm17npBNbrz4 +bw7ok0XYPze29Iwj17iNdR1Bkk6GzcVme2rCUntI2TU +-> ssh-ed25519 5hXocQ ulHpE+5leL6AXFAGz5XLcvyXAR2aGIdlgMdpXdOdYi4 +qrT1HRFhTcpd2bLAa2lcLRTQoYl1oc5OhtQ7YuJnJP8 +--- CAcwhDbQP8Z0CZgi8YYAxxPthQWGP8wqtSbN7PWe1GU +4֜@Qfjlcoح٭i3yiTpΙ}k hCƅYlN+R_le% +OKe +S5 aBc^0Bkw-p9d-e \ No newline at end of file diff --git a/secrets/restic/livre/base-password.age b/secrets/restic/livre/base-password.age index 8cbb8757d3758639a7e04a88e920d8acb9dea83a..5ea38bce3eaa8eccd0f6b966257e3af6b8255263 100644 GIT binary patch literal 1235 zcmZA0?T^z090%}72+9(Im}vAQFvH8SYuEK@5g}c>bx-ZqbzQp}a;y*6Zr$3hUDs_v zcp>@%f(gD6i9w8pAP_Y%Ku91Mjfoy$P6&Y*h{iN`u*YFJ0s*Nw7`O4z!Wn0Ww5&EIX2uth3X8U|aua zZfg*CAq0);A*m+AD3L-vEX(yM$DNjHVU{Y%vQjlf8ihJBnaQXXqs?>?zr&hT#9-TD zpz=XOit$CNl2y}9#*oWGM)PVK+fo3Y?Epx=Zpq;PER-w(l?4m3w1G)D_GrME4hUk5 zCDlMum>yJU2qo|`TtQ)rE!#0J5kUwftZ4{uJC@Gng9aKKE{IIg3#S!SX_jc%02Pg@ za7k|Q74uL=8*%{QE~)vcy;U9#8tH`#wXD*&ykZbUP*54B=dXxvgT(|5^c z3l4cUEdV`{&vQzezLJGWLsQ^_KmKRj0Ivr@Y|*+vRyjN zIe}p;&PKB)Plr(*OMnU}Rg@ubrgJgBN}HHUhLjT)haQ`gvq8U{K~)l_@krWI2p@J- zl^p{mK&qAs(<7>zRrN~0Yqrdk2SOznu4xp}Sgh$1L-dI#+U)!JzNV3Qp0yM~^Bmfz zvR<-WC%U-b9H?+I&5V;ZkqRSD0n`kz;T7{%-m(X(*+RVX!Wea*Koz_?Mwz;*2Bwa5 zgvAWKhERi*8pqLui z#R28wVI?oXG?)n}FX@f>potp|Cs!wXBRmq1$D_H0jyfHPBY|wKQloH(%QnM?u<&nu zJ3;!2oo9<~J0=wYA@b1d#rc`pyXV&5+7AAD@a%#0f4#f+_SPMr{&>D{YO)73pMUMk z`}0TN_~>^0DKRy)w_N7*QZt0#&Yp?8@d2w##ADuJXpTmw47pfb-`|#xQL;ZQ}$|HY1VeJ&& zT5^5gs+G{PxrxV~xiYBqB9(Y^$O;cxf?)&ER ztNR~)Pd^S0aVb~6(1cH7;)-FDx%3D$kv-R-vB-EOyU zKh-b7lMo0}V40<2W@-LlB3SxZQ4|kJ->^h8@*#Y}3?kuq9?-HM)4$*a@6Sgz^oZV3 zyCb(}Ii1Z;*8uymP-JV^_sxi|L5V~x6*d#T#9-kL0clL!Q^w`IN7ikfba+mM$3UG@ zRI20AvQn{%j&Iu|G~@zAtqaI7;FoYt$+=FyD#kz(_nM4nM0s80q0~TZHUPW!pXOTq zhzlieRmOv1vqgl&b}m}e z0~~4aqLj=FDB0+Zm3q*pjg~~Kx*i4duG(l2-r#>0k}iUkyH+d?IRscx>444FG!+qH z&y?C)M+u^Ef~XTfIxoi)jMYvDAxH0-FakEs{3z=K2${;(g}`dsj4sJVqa@{|=3s=B z2Ck3YTQLV^v>_7?SiS7`%ebxjWTM|ivt1k)vxF%&1C+w?PGG>S(@zpgIulZ9v5OaD zoh)?1JNMOV$oKH)h02OLLcW4AdP>hZg z;XaL|O^zId`GT8E(z=Wlz=CPBWem0wq*HCuibGjQgQhizH-XMB*uK zz@o8u29430C1eDKmm~)l;XX0uiyftzzfaSvC9H>q+l=Q_0@HRSKr_oZy2lGH|U$Z1hXyrQ-{sOz;P zh^t!BK%+HO7}sM~24t%BHct*!!x4>8BoYbdZL+RJadwn7Qy!JDv}l^Epxzj$$_mc~ z4njE^TZ;Az)lz77>gVC=$2VNQc58O;F?7irsU^;dqx09^D?Ihu!s*4GKkWd%nA#{D zVY}a1)U!2>wefP$>7nUzvId=266~LX)ORq1? zY&d%O(pyiQoQ>>m@49o_oxgDM$@7PiO?#F-+dE#F%iY??Z9B2%ol}qfIX5$X{gu!E zKK0>|5O#et5i7kssjl0!;?Y^>>O-u`&q`Wu_StR0*<`^;6LJauX3ux)=f6JMZUr+5&7bd8`idR2d+`Ia?dkX#I;pD{^-<>z9=&GeNTMr!iAh!1RbJLc2 K<9iHPJNYlu_`|RO diff --git a/secrets/restic/livre/base-repo.age b/secrets/restic/livre/base-repo.age index 8a148fb5c18058625b163ec71aed22a5c84de1a7..599dc38a86440ed5e290e49d5d50c2320e571a11 100644 GIT binary patch literal 1081 zcmZ9}&CA;a0LO6@1t|Yj@d!J#a6Y16!w!60puG);7!vh0%d5$~m;BkoTFc0G9Nd^y!I@Cu``uqhSzQ3R8yInW6i+xsw+w{t| z@S$D5)4c|iWzcmU7)AR-AV5n^A_1=;4f!w`t673hH6y3z6hE!JA~v~|obNU$A<(&) zxSL?zAwj6_VR+;XvEFJ5M@%#fqC-#CQzGeND{557tWXwQ)8^JI)6B^i1PHOb1y3!n zAtgDRF)bUhk&_0bfBv%>6fRvKaL0ZUNjyB_n1wf_7)fw96QRyhPDLgMGS)nN=MNT$ z8EiWYtyGZP%dL>oo4FYGtVKJUl4IJb{hSXXy$m_!kfLMRKCdxCuwoiuH_oI%Sh~C$ zg9KQmay8b?eI(fVX4dRs)t*%hhR|iZkvbG=F&?4RP((vZT1v}9>`j_=62Jnx4ZLvE z1iH}d3{o%8Yp%|d6$z|cq~_HeDQNP5$%Y*4W4hntV7v@jk2N?XVU?igqMN1EVT-iV zVAIPvyOo7_LN_!=q>wKK{CLG6X`}VA%_>b$vN^AzfxfjRpp9*Qt(Z!5V1qNgjRHSW zn62Os^`%>mGI5|rZX>pC*z2?hNgNb=Nv?TV=Y6&yd5cpBTbB^dSw&1^6(;Vuu&XPb zZnq0?;~iE)3fFLKW2sRu;&z}?ZHAUyDh8&7j^{ht0a5!9(YbW@yP_)%hL~T zzIg4%>)^XrPmXS1{PNo)V}I+7iG1mfD?NDWvyTr?Dc_x5_+kD#|JRRSkDhyT8lOCT g<%O@F{TwlFy?yb~!%x1ybMO6E?>~^g`R%8_0Vij4&j0`b literal 1081 zcmZY6&&%6%0LSs2l&AO{ba5!G!MsF!h?Gz#`dP0Mu=&Y$stWne;+ljVMh_jT{>nY9(3``II){;%xP7tgQxfN}r z6+~`zd^&bYXSOPfQ9c$NJyYYUzphPLazQl#EMh)%>ykHHEVlw9cxoeIBJ7yLsd{?V z7s~+jE27iO9WAvrK}z-E%KDGhr=^bi&^iWeMC*xj8I>d)83&zU2M5ZuZE0VVNPlQ#ht@A*w-b4p|2Y1pVFj?jTS z0$~tQV=n}^Oz3PTt_NAXACfq>4AR&_wqVZLU@6O{=Tp-yym1J5ox&Cp z>N9@SieOY-Wm=A-V*%A9lbG}?3G`_o+j!C=MpMl6r2%X%qUwAbC;L;bf-;)yI;5X3 zX`iXL0|Q71a|pw3e5hA&Ny)uwln;ybocBlzb z(UY-1nZttB^UiDTd`#vfRB3!@4YM-l^jYSPV>mNI-QdM>X1nW%?&juT1B!hxFzi-2 zmr1Z$YZ0Z#bwtPTdO5($rsrhTK6B`iW28;Ks={UdKMnKeJ6nT%gsvr-!w)-auq#o| z7>lY<5)o6WK__bruv(CA*o%k5s^yvMnn5aSQs!FFpp@E=bfh63P2H@xZfgC zI%h$v-EKopB$1gKrcEIh?pBue7=rzj*K3FR${@#S6E-E8I_C{!{eddiV0}503u=W|wp5 diff --git a/secrets/restic/mediakiwi/base-password.age b/secrets/restic/mediakiwi/base-password.age index 29976d0..f7742d3 100644 --- a/secrets/restic/mediakiwi/base-password.age +++ b/secrets/restic/mediakiwi/base-password.age @@ -1,19 +1,21 @@ age-encryption.org/v1 --> ssh-ed25519 vZ8Vgw /S2lHMzaw/CJEiS2QEb/isn3o2E+55CBguU1Asl+BEY -GZyDQyGN+KfXKF9j2ZuQiwRGzk0MiNDR0mnfkwch96I --> piv-p256 ewCc3w A8vVXBLBaMRZtTQemR4OX5jtS2SLgnDscPg/8xO+oI91 -qoZeaAW133B3g/9sHpkg/+/xUXCsAGf9XflHipmu5kM --> piv-p256 6CL/Pw Ay7mJwS5MDrqt7TxveaD0geNX+Pj/IPsjVE7Nm1jUJFg -EEjfgte0yvdDkLKAojDnxvF0uYyBJkEnFg7wnE46EC4 --> ssh-ed25519 I2EdxQ X5a7RJTISHi+BGDLAYdOR7QEux7PrNw7qBrV3WtgXFU -+QU+NfZbgiiBLzY6F6C80HAgCgfWyF0MXF3tWiQ7T8U --> ssh-ed25519 J/iReg fGNWtN3KyyWY9bvPxRJUT2uREw+M6Lx9aLRESMt6Gko -HAYLMVVqOAXTSTyKTQhEr+1zfm2QWG/1qHkFpYLtpVU --> ssh-ed25519 GNhSGw 8vyv5v+TJ5HKEeiMn70Tuk+cBq/Sx0/SmANRkatIq1s -xYJkZU23q/Xyu2wppSSDsg+Pm5Vhf84m8Vjj1Xb+/BE --> ssh-ed25519 eXMAtA 2ErQ66iy6EMp9Y48zs50YuH8rhlVqTvhFwbG3BHrg3o -qNLMIkqRbkT+pHGnAM3JWbv0E25I4SXYi02NfzH+ofA ---- mps286qWZ2t00Fa5c73K0gIoYSQc8NSX36+yFG4Ukbo -kd 3l})(3WBz|܀f:z!6 mlڎ@Wܤ_zؾcܔ}]cDFtz+;zՇy'}cr < -#n@&Xs>Ԛ#C:a&}㞬fi2*U{D- >b{kЍ}0(\$3Wg1h -n#g%r-1/yoj~{ZC0 _Mu~~EԒ#8^oR;MSA= \ No newline at end of file +-> ssh-ed25519 vZ8Vgw +B57yVih+Nm3HaJfX53NcVTIUChaN4QRHpIhNRh0wig +2Icnq7jf1+suEjUYnQ1ABx3gdTefT6eZJ6KVEupA3Xg +-> piv-p256 ewCc3w A2uBciAE9cSCBJWLowDc4d5D4GQsroJ1EX3BJsr0w8+T +FUJZET8ck71xPBh2eWF8930JacE89+R2n2i32hFhAlg +-> piv-p256 6CL/Pw AgCcZy/RvYFO4WZz5/Os29sXgkDGdDLSPl4ZBdJjONPk +J7eaYHcgnrLEm4PMVh8qXHB8g/qpx7huoRgHNgO2aXw +-> ssh-ed25519 I2EdxQ +7WH3VaqC48ABO8Wpcb7hlY04lhKetfyYm3+62JESFo +iZXrtZ0+UFdvcn75ceJ6gxdXc410BZdgnPSaJsG3muA +-> ssh-ed25519 J/iReg rUhw71duo5PP4M3ySN7xHW1ebsXJ6iYFdj3eZUriGgA +pSAHL6A9lCJ5qFqK26PJLG84d6nIs4psL3ea5Pd8Tac +-> ssh-ed25519 GNhSGw zRjF+gRLm7IibS6joIBlOInFygsHVo7vf10IsYWx20o +93aGm4IS9bc/noU/2l3sMAKJbf5EkDU5gDDxDDdomak +-> ssh-ed25519 eXMAtA 2QLLIa6MylYi7h0KOyeZCxQQuyMPtcRTWu5Mg8oRpSI +VwWCwogmcmAQs86ABxxbuWdK7XWh071HPdQdPeeS7I8 +-> ssh-ed25519 5hXocQ AIMNW8H9VIA/wabPNGB54KGv2OT4iUtX4b6emWTpYk0 +vIj14LjtUcJ2GVDrStg40xHjpkEAkv53qnXdtaweuAk +--- iqSZN2bLs1BukHdpv8L48ir+5W4DeJ4ZviSn2hj9Eqc +w? +pjsN m)}Z,!|3#K֖^&o,0/K1J ;'-QۜG +@ 1޹?pز .][5D$+ #9ۛ$Pj_˓Tqx9sH8=;1WSj+"Hݐ\_1ygGP \ No newline at end of file diff --git a/secrets/restic/mediakiwi/base-repo.age b/secrets/restic/mediakiwi/base-repo.age index 8d1c3698b6d8fc693e21476a3dcb14842caa29bd..65e03c897a280493a5953595965afc1407183573 100644 GIT binary patch literal 1090 zcmZ9~Nz2>>0LF1~X=(He2!hc=4-V7pvw#*enIx0R%w(G+lYo>=_DLp_ZIa0#xY2_r zMK3SXg9@HRP*7;yD@f_3;H55zR0_U&u!;roBK7Uj_Y?f!|2&rOw%x?eo3so!$(c>= zLlpwF&w@n}v|R@!2&4~IW>52LkfHUcU+;w!A2G~4DSP%DSqj}<9Mwa}MqFeqCgqO7 zN!bQWRUkdpOFU%LpqLQV)`}uXXV8*kvLp$-kubuDXLE(6?`mmSwNsSnfNo9MSPhb} zok^Uft^H%IZp;Z@p47;_PMKv(VHOICC#jDv+?#)-vV$p|c- zg|U&XWJLI@AObkE}kbyu+EY-EiSjLtS3 z=1!JZjmqfFhpx983=-+m-bKsbeU`Us<9%#g`NuVO7AR~DQY0JD&Dc>`H;h8q zuvpnBvaqB=(x>csN;34Mp+{VR7yuTI)~?0*)A6b|M?6R$GN^3Yxt%mS@@^BkA&6Y7 zcZ=e<<}Rj3CjzyB1Cu*(KkvlJMk{C}?*_f7FBuM(8|Bs;%*X4#uVr;$09q+(Jg$#Q zwu5C6OlGsd>efx@K|Mg$z-h27AYd6rJKKS?c zFR}3J$w7Ya&4X`5;fd@s;QZ->q&fA+nHz6h{8W8Zc=gVSM}EF=^3N+zKJfXw&;IkR zaP~{@{fFgv<4Jcaad69sUOb`*^hg literal 980 zcmZY6&&%6%0LO8h474bSsDpvh!+20bO}=S*4O`4?5 zZiAi1&LYSI0}mn|ym=6j=_7i1z}t9ammxYm4$;el%*UgTf5D6Q=QH)ahPQUpeX@n| z`i(gC(E`*O@6@v_Xn1p+BtffQILyQ^>&$R%Fg>p==VsU5&!sVRw6Wys<&dVwG9AY4 zv>;6unN~(kMGy+lW*lk~)OxR^d(GRIailkj8e-4h*T86?jTTD&$PaR zhIyWtL;!%s&*FWejJn*wj5j#(AB*Dq=;+MaOzcqZkTcAn%I;jWZAlRq87^7nQumaF z8w)t^=klbLMYVn0wn@Y3B`R>CAB}0Y+eElv)MA^nO~^L|zDH%L5};CeenlA<=R377 zid8z*$0!&i;WjsxFw63m2321z$tjm}7J?~AVPf4Nt_j7slR+ovWujeB%1D3(OuQ|% zlU7{Ccm|G0YT=XDI?lRvHZmCbQ549T0i032smjNHJ$9 zK|T9y$)UhgcK9+_^vNVk#AdE#?7*~D-QS=sZ#s`a(vC4`|F2CN1e#RV=PupN5ND|7 zR14yBWq}YF^b;LxwrpQ5q)M%N!2%fkim9!2o`LC}@AE^nbcevoLcQw1{fg)teW>I` zx?tBHjjDouzRj}~cFWegZBasKuJjxr4=KkrrS7l-ir*Y?K28BF2uIXPu!EvQ)Upuu z=pb%6PrPTXr%_S)6H&Mt!sjt4W%YBq5~Pa8f9%VIfWt zjDS57SR!n8%8j=XM<8}<*B;)QNRNJ}uN>dHd*_3T58l3h2#$aH{<({vaW6cje)Y}G z-#&fv&_DY6;N)Zd{V#su9xncf?|KLKp5EO5?oH6hArbOXN5}6PH*Q}5)d_xm{P@nzm!JLh S>Z_Mu|8RZn{^6DA$odCEHBFQN diff --git a/secrets/restic/neo/base-password.age b/secrets/restic/neo/base-password.age index 6e8ece2..8b515c3 100644 --- a/secrets/restic/neo/base-password.age +++ b/secrets/restic/neo/base-password.age @@ -1,21 +1,19 @@ age-encryption.org/v1 --> ssh-ed25519 /Gpyew vzIXN5idcMtsojCh3QmuQaGocEXSJnCExu5P/H8DWC4 -m56Zgc+KTynT4EhpxU7FrJO7HIt2dHWS2Pl+6mJff9w --> piv-p256 ewCc3w A6RbIBQlx/jaE7io1p1gVzYJTd2TEiFeBdZ94zxeIJV6 -z/NUcvBuXgmiGSx4Z5X8zG8zB0LVsqAJbE22OKpSYdI --> piv-p256 6CL/Pw AnD8EhdgSmuQJ5Rtir05sGBcPZQWKJdKu8ac/BY9vQdM -vISoiwtVWC1Tf+67p/7MvkjO1bipmRRKLmnALsnXcHs --> ssh-ed25519 I2EdxQ Os4bSzMGck/WjfSqKAP5vrjNKo9+8PlcPZaZy951mlE -wQrMm4i9SqZTMF9lhGJ/V/EpL6tHDyNe6+QTbfJ/+TE --> ssh-ed25519 J/iReg ThHT5JpBkmeTQeG5F3Xjv+y8t96fta7nFIgweyu8Ahk -KZZpaePhY1AZJFxIixkoVTJs5/Q5Pn4mwDCbNOFjbAI --> ssh-ed25519 GNhSGw PvzZARqPseMFNSRpMr8SvONtdMWdUy31p1U3kkpM1Go -paGjAImxXll6l/3WM+0cV0CXahfESDb8BBlME5TrXYI --> ssh-ed25519 eXMAtA Qz0Ms+UA4sWtj0S+ZEBV0KZquljvnrnTZmS9Ie/4mC0 -4t2ti3K1ecrHi1a3b7m1HQodnudwD2mEiQ9PsxY97YE --> ssh-ed25519 5hXocQ ngLEw9YTJ5pyK7FyQI87YjlTbIDDHUpZaoEXdUAnDgA -pprWd9Mj2kxtrYLKTNkuhVCLLi5dHGnUOyy6/3LmYRU ---- h8zaWuWykw0ox/4XAfTkOGtbuNDAKzYDnM+gCoOP6TY - DYg_T9\WO8h)7'm^4l~ȩ]Q:סNHEa^ҨV -y>C\ PXë#l4$)=aMT8Qw{9?&J1d=W̪k4y>t)v&wuԥ-JGy`mUS^:gt1{M -2|}PFgO`^1/.CZO6JodE$X \ No newline at end of file +-> ssh-ed25519 /Gpyew SkXe5r17rrq+GBVVQcsS2HHa8V53JhSSVRwo0I80s2k +ByCrnW2ibcdQGV0zmtEKZlFDKoI8u4gTc77w3kFMZss +-> piv-p256 ewCc3w A0E0tbh4tSqcU3KBVcFAoQVEc32RWIrYWk5OXMvQwfkf +d2P81IpR3oERun48wQgg9E5jcTA4DeswehepzjzFJws +-> piv-p256 6CL/Pw AnGS3ZpZWsqtYvfNUzAddYufB5FdCOI6PrT0VCTF1xlJ +ks3tGbdcJQIfkitDE2s1qO5hsgNzduFI4c61yTJi8k8 +-> ssh-ed25519 I2EdxQ syOOIizaXU7HECwvXJtlp7iko1dthGyUVOqGbxPneAs +k5N67qOOU0WNjnjD7qErclQmGOYDTzBFSaNzPrZGrhw +-> ssh-ed25519 J/iReg gU59UxyuUV1WOELP+mGUiOnSuqwhOPs6/bJJf3WoACw +W9QPTpHiR6SxX98iQo3XYw7ryNN24UHnTI+oaGdd968 +-> ssh-ed25519 GNhSGw nzr3niNttuzu1fgPxj0tUYl/Mun4/arC2u7E0ObgrSg +oScU0Hcv2UMy94KePmAmyp9S60q/Ado5h0ZQwEcX68U +-> ssh-ed25519 eXMAtA eEMxYKf+3++SGhnHVnT3scopo9+n0uRSPJLcsMRt/0A +m3nf4g0Biot+GIWcNq/l/N1YlfXVosBnrXvHHZH1g5s +-> ssh-ed25519 5hXocQ bpg50d7Um9oKZHZQicvPMH7k/qx0crfkk9sJBeMCMTI +aFeabDzRmyDLxYlQSyjWPKAbmkrRNKcQC3WhUAUGGIE +--- Luhmj6HyNQba+/ldSrSNzC9DBM9mp9QTuTS5iare2u4 +`\/S%L4:|i9adn*{WxBDˇ>+RT1RpgV9gU?FTŶdFd駲굧*U3}l)*'N+f*(Ugo\lF#襤@`e=mgXw[$*7rc~oYA7Xı5e!h-x$Dȴc܊@!;%@6tg xCoNYN}"~kEk8ˮ=lP \ No newline at end of file diff --git a/secrets/restic/neo/base-repo.age b/secrets/restic/neo/base-repo.age index 76f8d7a34fe4fb675202ee36805f89405b7f60ae..eb53e2c54b213e3db276b58b10867c8f032071a7 100644 GIT binary patch literal 1077 zcmZY8$*bdZ00(fN;K3js-UA2mU=AJzYE6?SO>!AYnzmWmEKQd*?;vTKCTaHknxzrO z<>G=McvVJF^y0yb7exdal!qcXco;>{hoE!wAh@6iK94^Cg3tH+g|Fp0Z6~(#L%R2q z_^~8+krIX4*I)p=wzI(q0;OSOlpdT4_AJy>7D0<~QytmQzVbfnjV zet@|&uCE|Zbj|&~YWQTR4Mh+GtEMxgRuK8$LjURhN$s6CZ5%~ST2!;PZl#H*t_CN+W|@iRw7(pQNRY9T7&C+ zbyzr3PZA7%QwNcRE+mQS%Skp?3&P# zWD`h>b-J$Q;b_ZOYGEB#LCee%6)7Z?;DFY6Q$8SRb{3lWWo;yT`lx~vi>k)iAi}sZ z-mRASD%LHG=nQkD6_3VbFZPr?-E3wHR)8>_HKs%0g4~{VrZZzJRD+st$qN*V#ys2E zne=pzkYQ`^za<@G!t5E?;7ZPx%Q3?#nvN{B986c;HJTaY{)p$JJPG)wD6DYIxR8bw zY9$%VY$TWmA!;R*ze^dE7Bs$WWUVj)WiAaVPxe-qyAhs|*cwa|%cN!(xQs;WZQvpMeG@rmjOF=sist%mW^6?e;hY!5KCG)(K_Z9a=bPk;2|1J4~j_4Sqe z-hTd}-n;Hq`tQ>x=eKU%bMw2S#Rsqa^2Otyzy1UMp~}7b#P@%mou9t>tNdc~?UnAG z^Jh9gb-uw~c-jB-R625QC^z4;KR$8we8`p2kk9_jS@yU&^&cc5HRjhBd literal 1077 zcmZY6z02bS0LO7eRE*#`=pZgR9rQ8OJZawArI@67ZC;wBNz;Hxn&!Pp)1*n7BB;y# z0lFx1!VNAC-s0qm;2QcgP`!MzC0wm;)Pum z!7Ty|CSYC1gJ3hnabyg_eAx#bm>;2&HQh;DI+J*ew^*rMWT@{?^K*9h5_y0E3S4(GH{z z41ZwuT2^xSnzIfKm8^mhwp~C7PbkN!ni ztlT?3a4w&=o|D8@fa3)gnXI7>#a?ey+C}O$t-*DK>WyqB-appAR}_cr=BR^y*QE)& ztfZJh^{sncGc2Fl^gKkZE|}6MA%;{(2kf!eM)rscdQ3HDh`DlAYZ0#s-y%=p z%t9~=XIp)kd3AL0GYL+NAOh*NJ7?|Mf^@%`K?bS8Nw45~U+CvZ8{m1l(sJG;7qDIf zwmLjVNgpi|+1B8WDYrvU@*=$73Fnwo<27e=Y7~ymQ+83~)!5`aQ05gP6{mjQN>W-M zAkpRBgUIc7WrNxjUP`rtp3AeCq(#6N+EGvI%c^quBAi2TqT<HbI43=CNEYk-bdGqPFCfl!X-jY7ObmQus z4}QM<(u><)&idc)G39rY?>~O~)$GnsPrP&Y%Clcydgj}^_s6f3uM`h^4|>llpWpCr z-}hBEJljovkl(xV;q4qgOvO X&o*zcjXyCSzxPM~ocr?4CqMZU2d8X* diff --git a/secrets/restic/periodique/base-password.age b/secrets/restic/periodique/base-password.age index 395572e..9fb9dde 100644 --- a/secrets/restic/periodique/base-password.age +++ b/secrets/restic/periodique/base-password.age @@ -1,20 +1,20 @@ age-encryption.org/v1 --> ssh-ed25519 FtI9pg IvNSPTRYe4LkaPH0oORuCQt97urcG+ZAbT506tpUwzs -B2OZ+xmNn5uzxbC3fZxEOe9q8mmsQ/sVn5vnPL4W8CQ --> piv-p256 ewCc3w AtHfFnS9TAmkjv4AAdqqG8t6rOBMRf6MSmzlkYM/ipa5 -VAXUz8H2rRPY/u0PEtWERnMA7gM6CHBzFghxYZ3EFx4 --> piv-p256 6CL/Pw A3m5XXDVrbxuZF1IEXy+WWLvKbREJX+vm5cw6Bs94dlm -fcL9jGocTsAAw131d2j220xVeV5A8SBP9Lsr7v+Py5E --> ssh-ed25519 I2EdxQ Ndto3EUMixgz1+CwXzAmDM/aZj46w0L65zxHL1XL1D8 -5SRZO6hGuNRQxvBAeyoazc5TcBfGLHRIT9taRvSLpr0 --> ssh-ed25519 J/iReg ws2Pq45azL74SrCMWUXh4efFHkPUlioVaSKu6jnhcg0 -d9i9mjV/PHae53IeSdSZLHY41idxfbPT0q8MzwKeO7Q --> ssh-ed25519 GNhSGw j4H4RyZ04R5fty9UsseU0KEGg9vXB+VD7pwWm4Fldwk -IW/cdj+p21iAH8qQLWMO79mZkC48JeOjaQvhiH9afHA --> ssh-ed25519 eXMAtA J3hFWvhvLf+6yuImvebdy//+DxNER7F1VfA1hHOZIik -5p8dWje8b3PaKk94xHsLcztNtfh5eP0U/2Ym13IL1AM --> ssh-ed25519 5hXocQ TNStI5ozplwR9QBixWnWGoA81jp7R1Wky6X+uU9zREE -CbWTew0F4WCO/AtqwbV91zhrr0yqcdLZVokiROAqQMM ---- WbwYGA5zi+fY5JiK3sgD9W5ROhqYlhX90/mxu5NaPWE -PIa_\L;#3/XWIAE`'/c(J4_nCnh/ґL`)T= A)&{{ -{"rlMcs`ͯVPums,y01@[J"@9׍_؋:HF]QMvz$ޢBB/Y&) p7jXPhnysC)-N&9qD`Ŗ?MpDO \ No newline at end of file +-> ssh-ed25519 FtI9pg mFM/P3/8639icKTq5C5M914SwyAIvOAif2TGfDI8FAo +BwSERqsJqHxghF0aRF8fn1H442SM59tWTGQMTJu/48o +-> piv-p256 ewCc3w Avp0PUDZtAwZIsoKy88bALV+X9Plj0qLiAJSbUYAPm92 +jgGHa8yZXocULMAZl6riyioNTNDzhtbjJAmspNfHyl0 +-> piv-p256 6CL/Pw AnTZu0Edin9ryS/ggUEnxS/ScQ0J0qGoZjsaiqYG5vKg +66jnskFGeBKnPD6i+aAN4754Z9mcDABtjxRBayXeXGE +-> ssh-ed25519 I2EdxQ QBtbE2lrcU5oa4u5uW0m0Vr7O/trHlGmeelI/Y4PZhI +9cU06H7UxCl/VdGLs+Dc7urWPyvJOHu8bHz2k9wF95E +-> ssh-ed25519 J/iReg UWDYUCeOqOvddu0sRufX22829wmokL4EYjUZYgppbwU +cIU/3zBJegsxEybfVe6F1A4wgv2EZDYZfWaaXdYQdrQ +-> ssh-ed25519 GNhSGw DZEVG6RZ0fjheOguvFJ+lzBut7K7SeIzTd9eynwhpw8 +tx+nxAVcyzLJMi7aodlOzpMzNdKktnvnkGXXgpRVYCE +-> ssh-ed25519 eXMAtA cIKIxOQQyEzsV/qjc2AyTfZYcE+gSvqTCD4Hub4VYRs +HjSiSCSC6i29NOZ7xJbyepne4oJ+ZDtj6fQEBdsZTnw +-> ssh-ed25519 5hXocQ qgc0lYbCbQaIwS7oxYyykznvcXE9aDc9Rd5E+aVJiVw +S4xYinqe6XlHbJy9LTwwrqGgR9hbv0F6Wu29AyBfxZ4 +--- NwYqRtIaNhCnzZ2/8kbgwqDazv81MRFb0znpF0CGWns +볥f]@ۏ H=$7pH-z&dt腖R93/]! +_ا}Ȅ2}Nq̊HΌocNN6ɘCXG"(>["Q~]=9o \ No newline at end of file diff --git a/secrets/restic/periodique/base-repo.age b/secrets/restic/periodique/base-repo.age index 226fc0a..b7f0bf5 100644 --- a/secrets/restic/periodique/base-repo.age +++ b/secrets/restic/periodique/base-repo.age @@ -1,19 +1,19 @@ age-encryption.org/v1 --> ssh-ed25519 FtI9pg 699sQyv9A2DsVBx0u2Q+StW7VP8+DiUX+uUOZDP9BXc -7xQrHSWDkEGu9iPBx/jiiDmJ8+KRSfeeLqjtSHHIKxk --> piv-p256 ewCc3w Amal1a3gRYmqp2KDdlgN7mWzPzJG4EHeDNbBnDp9ZK75 -LtEh4MK3tpLh/IIjuD+dXzlOeNK2Efgjwy8L43A8/gw --> piv-p256 6CL/Pw Ap5uHl9tOPVkCB5/BqxgKGhtGjVxooNiVAAagKiKfxO1 -zhMl4yVJOe+dzjwcaYQBCjYs47iikeykXFznYAUoF8E --> ssh-ed25519 I2EdxQ KXijPMp2Yb2ccGAfmX/xw3TB+Wcw5+WALWLwE2MUJF0 -X96+UxQo5R8+sZt0uCdauFdnDUP4otRs4QlAVWbS+ys --> ssh-ed25519 J/iReg WZykIiadzSf9FrirQODpSPJc/XMggldDUuAUvSA6kDw -JQeaP8u8fo3l8xbZYGTyEft39KYRVYzPXvtQOk9hhqc --> ssh-ed25519 GNhSGw Iwyy/AoqxB3ZP8L2fh/tIUMo5VhNnB2A2qouHzj34Hs -FFzA/w9lm1EHubBipx6kqQAPw0oBjiFVxkKoZDAqM2g --> ssh-ed25519 eXMAtA fXjp2bJasqd3A0qO8JHThWgchw9Zb2lnAA5ux+z0+Tc -5UIgx+fg1a5qzEeXHmCzVPlUy3T7uwosjDN5DU3acCc --> ssh-ed25519 5hXocQ VaoJWjpaDZXOvPgw0oFWuvFWJm2U/wT2KlgaPG7QggU -NpqFs3pzV91c+5dMcdsjHz3br8jxKg3obeMj1M2NBR4 ---- 1XXLm39XkQcmHtbW2Gxhw+NtGvB6vaZtavkwBYL7sZQ -X&ỵrNg:u!`g3/^11y/-:GS*P7jl& ؠw0>Wg13m-SZҨXi]Zb6%ZTIu1:ҵ33~Nb \ No newline at end of file +-> ssh-ed25519 FtI9pg 1OsLKT1g76VPrKyRurdJ8OyteSnGzX7TEckPmpwyf28 +0e1qBff8CupaSnHKdLH0dM4fLbtNbg8/X181HGkeZd8 +-> piv-p256 ewCc3w A/1UBm4H0P4jKf5SrGigIbrpehEVsBVtclbR8hYzk/kX +gQWudb9QEUbgNiACN/stOoYlXC3RFisvdgjmx+y/Nk4 +-> piv-p256 6CL/Pw ApfkwQMAOkbndCdDNv6klT43WAPp5+K2ovvjg3JcD2qQ +WSOnTDrQ38yxV5uXOWJlZx1UNQHzAVUcOZ1mmmvEGvw +-> ssh-ed25519 I2EdxQ ehTI/3PegWs3Z8lu4fuAx8fApt5VN1/YNQ2F0SQIhAk +1/stBfSx23RKeQNXane6xxYA7PV3f2MVwZXnqDxoxRE +-> ssh-ed25519 J/iReg Bt+09DdWAbLCioXpRoUUKVwH0Z6AZG5eB3i85c7rWFo +IlebpT5T1x6db/ZEBYNWijp4fO5lWaGn34m0yM78JpU +-> ssh-ed25519 GNhSGw bWO18coroB3xuOTwWz7JkNh/i4lD/BKH3igQeNZLHRI +974jSGaTiBWr6A1knJLYzxAYF3f5fMT+Kj21+m+1IdM +-> ssh-ed25519 eXMAtA BG7NJTEGorN/IlZdqssMeg2DPRu2xvU0deiqKpZzLys +mwW8+8OhAyV7amdUvupTUi/8x1huHG2YSzCOKZ7ygx4 +-> ssh-ed25519 5hXocQ MhZCOVRE8Mq5o6jDpTigPsCIqhA1MAVaMBp034lTt28 ++buQ//Fz2W+I3znsMwhYtprQvGa5J7az8LGXqyvaC4M +--- fPBJCK8gXll57jEsivdG8cDG940vluQrkOfR7FWQGoQ +d<2܇vNVڈ,úFv!={\>[H9a֎7 kYd`ɤ2WLggCkʫU$-OB+I4Gp zsENE7BAO^BfS$&P3SyK80_ej*BoQy*kuzR`Q8dJuh=@i}G3Nt%K6>xJU5hfkXG3WLef$m6W{;|YV~rfuMJRsAt+5clE*S`9n>T4z-WyEfzj#! z1~&7yokespwDOoe&Xl_4G{Zu+4GJx>K1_qIGWDJPF*&z_Jyq_t?LcJ;AvYD#!ozzIJNfSOW(g?PaL+n(=QmTtJ zq$4?MB`R|ribWH&m`rsW0fS(^QiVtkD~AC}ha}il^TgO>bOUrEtsTYJm`U~ve%;_T^-o9h`?CZ>Bx3FP)*YR`23!iU1zUHU|UcL3_ zm1nMZ8jp6DzHd&?FS%Kpx%aWd*R3C(y!+Za{J#B)w(Xz2OZ@{|;t!r8-?h)&5kGkC z{+UBJckNvJ-V-0cw&kU(4{e?NW7AZ9`=7wo%a>LiyY3%*xc=jT^DF1(4xxvsUsiwn z&CJw|i`R_(@wdN%zMfb9N>83RIkUNrPT!c?P0u}b)Lh+p_JI?VP3FqvqF~_p{s&H3P?N^^E@q~bkmtkDjU zLBdXFkeCwUG8D$JR5?ednI>0@teJ{{av0fD2g$A!O4%ex@jVhmY>j8^WX1x7dLfX< z1T56rySA?|aYO0aTIb5XQsG^n08P#IaHL|3Uq zJqBA?kx7%?u@HlxsyzkgWXl;Uz2vy!x};T=$YRlS>sCLXa!J7yMlz2G7%?EEX1lov#B!(k{vr{oT}5Zl1X)-0>oOm<_I;LGb&ckM7crQK!GF_ml2oe3wG6k zT6II=B;U;k#VW_!DT+q)8&>Rt#Gm;(BquNQ(F)0&j^))f9 zcQ|w?usF#yVF7sX@cVau`SeI@?Y-a39h@EByuEey!hw}LRz!>3T}Nl};-nl$kczAyGzINx6>_lhXo0pAE=oNbT&G#xNiN_8B zN4M;h24^Q8@&4YB`2Od$YcdaAe|psmfgXOiERzTD#d*Zb+|&reA!AJ~585BB7f z7jJwtcW7DnLho_n=A+QIB|Gwa_EpYqn?8H(#jDdxuYG%F%iiQc{2sJ>>#O67*Z=?{tt`BzJDj!(%<4bqs*E37sc=h00C-%NP`3%48*t_TFH}1dkC3R_b z)&1vUtIy}J&b>WxxctSc>He`_m$;^|ID7iH+g~5wda=Ig2C!&d^|>YLsgGCe-oxci LE3fSSp)~vlM9R8{ diff --git a/secrets/restic/redite/base-repo.age b/secrets/restic/redite/base-repo.age index 3ffcc29..ec0c70e 100644 --- a/secrets/restic/redite/base-repo.age +++ b/secrets/restic/redite/base-repo.age @@ -1,21 +1,19 @@ age-encryption.org/v1 --> ssh-ed25519 hTlmJA FwyYHqXJq8FnP/kKDOyZYMsEpOVVvdxcPka7dxH9TEg -hKPhAZz5/6DP1ugpv3bHOZrbSoVs0hpZSP8kycw0hds --> piv-p256 ewCc3w A0NZ/VH4wQ07JGUjRnD2QU7VlrG4zMeVzHa7g46Av+jU -qCXVqCAtOikfPENz7RJpy0PTdTw1tAwusSWh1iDlVT0 --> piv-p256 6CL/Pw Awzu5nbYg4GuVnEloOsPVwQ47BicdnAb4sS1mG+0w/Hg -CeDZkaghyrRT4Qokg6dTkDLrwND4mix7dhFgMEXzsRo --> ssh-ed25519 I2EdxQ MrZNzDREuwEhfu7lU21VsJ02Q9orNM0TPB87viA78XM -NSlPC8lW9U2ppLIGySpmU0HJpemN+GUA74RBFhnhroY --> ssh-ed25519 J/iReg ZWPGgqUI89NVHp7iLK37iRdwBGroJ0pDxI3ZMeIJ/Ak -PxJTCoNmF/c741FTeXYsjUjogf4/ZLZU56IoEKHX140 --> ssh-ed25519 GNhSGw k4VJGNkwALEyUJfqoWNjm7gVS4EL1PDQtigjrJyKJ0Y -f35rY9JCJSiEkXEC8E9O2e8RqikKHL4WG91y+Q/0Dxw --> ssh-ed25519 eXMAtA 4exKSkUZbK6IGNqms3oXHZjqxdanDxruBIWzlWkud18 -fikqarrrB2wEAS8b033Cp2QpAGxy1SGju6wcfcpgWPo --> ssh-ed25519 5hXocQ A7y23nvH1k2eh9YhzkDfTX8BTsds6HJfTzEPgP7A10g -CXq+VQurL+CrAZKu9ycJp/iSz/S8CTP8F00OAhNzuwg ---- TZLMHnfF4+CThKdhjtmeSzB/66o6MEV6r4Fh5CzEkCA -j`3H1TCZ6/zR+qрȲÜ QFK׳8,oH,PDnfnTI\Yn -ﵖk -f M0Y`.\{ԍ%Ʋkp \ No newline at end of file +-> ssh-ed25519 hTlmJA kFOWAzDbkq3Tv4qAOlpvOsPSIkC+du0NuaVlqHwtFUU +nuOosQt/u1aLrnbYP/HfYB5kx707tmKG9CuA+PTi8JY +-> piv-p256 ewCc3w AyXlgMSHnFrEfp0uB41dwtyhXIEaECaGU7zSGQfih8qR +VAUOEoFKwHLHqVZDp6IGcX9UKqeEYgElENu03DUdejM +-> piv-p256 6CL/Pw AqP2EQ964r6wmDXQEBb8UDk6SsphgmkW3IlugcsnDi0d +rkWTPELcoOf4i0gKcJ4jiAsBIOvBIVgrNXZ4KBn7uxw +-> ssh-ed25519 I2EdxQ y34hVqcR08tV/hj3XMydLD26YkF+GTlRy/RtQlr4/xM +/ZzHs1EnBc+ox68HkaUbu6aYxrOLSWBavNQNM232CFU +-> ssh-ed25519 J/iReg 5Bc8NqLS1iLcBdjCr9aepG3PW0K46YBYw/g7ZeQWunU +1aiVwju7M+6X/tkDqREtBkKShQdig5ATEkhoyyB78b4 +-> ssh-ed25519 GNhSGw CnpiJlLIgcnPdRHOi6oZcholAnbJ4YfN/G6DDlxHUR0 +iCDP882YOImc/dSQ0Na5KzA0erszxNGRtWUDtypuFwg +-> ssh-ed25519 eXMAtA BhLH5eMtjoznm5pZRAUYw0lC/WspJTdBad5AwxnGiVU +HEtJTTVyeWZEzPBMGfcUpMMt51sUyXiYYjuN4oU1pIg +-> ssh-ed25519 5hXocQ hfDNuyyEeIA9RDs1+FwXrFQgiaGzaHs6fkprRitxEHk +pmc9eCpEWu4FA/cbklPuRxbg97tfG1mcWLF+DOE97Hw +--- ZCTOg45uOBDseDGumu8mYOGjI9FZdaEBvHTev7Idn7I +Y_"25ZR4S_8½h ]`IP*ejR}K1DH^&9)0ݡ&tC֋@-H{˞3דāiɷǶOm :]M` \ No newline at end of file diff --git a/secrets/restic/reverseproxy/base-password.age b/secrets/restic/reverseproxy/base-password.age index 297fd73635b8057bd45266a6f1d3f8de9730fb2f..c1078adaa86d6795bce238f9cdd765c8981dee4a 100644 GIT binary patch literal 1235 zcmZY7-LKPh90qVrG$w_phY<86M0PafC}XEvw{B}fPSSR7-TJa_-P%ovZe7>4+i!hc zUlu?JG0~{p7%l(}q9k4rUr=MzDC&hI5WP_WF$N<6BN`&82TTYkdI$Ui-%p zTj5M|f>snwwg8a1Y2ZUOJmZX1x5f1=mqYz=i7jJo*AvtxoU4y2T_%X8@N^c`+Wvo< zsd+IILDLb%C*`Cc!F{G;kHw12cG5gntHK>Nq4#SIG%&1&CnJ(oZP+rOPh%Dh`D7I4 zfIxY6sniN8RR^RRq!^DD1t92MGQ-h1R*wR~s%CGp(j*O6Z&*O1MGs7p8IdQdT}EYj zH9gEt`lO_HTdfM*HESM=$zgNYhblblB5gVVMyJ#;B>{FCjq)T&AfViKN~&8AM@r~Q6y^BqViB8=( zKzJ58;DF{7(ZraqqjD<&rwIqbJ5!%akVvN~y3wW#V}*EGQ~*&xCOxH&Cgh-JW2Dm3 zOaE_n(}%EwdeM*c3bN5bI+Uscsgs&Gk{xhyIWrMl6E*CTs0w6_VVJg5R3VdT3=JF; zQpvur=X*$PFogv|@`)l+=v$g6={elv!|Eh+TQ)^va2Jns(;ZgIle}4&8k*ASQB%0u z4F!&G<`JK2gHTtUa6$_?or2)3%UgBEs&)g8Ni!x$r3GuMSlGZ}TZ1fHFj~Hj^qrb} zn}!Y~L*^rdP_ar}&J#!t>7!0sO(tQ%ZX`9$@$-3-Ob9SDw8McP7?2C+jii?iMhe5~ zjhc=MqL|c0isO|iH?6mu69S{kbV%lTC>D!F;uvgntAvT-2_hL-*_=X4AxQZI3V2G1 zTSm^#;p4C(*gUlT;BjN-`-_;ko4=A;3#irQ9jo?zxnsqaoN|WSb$ahIbL)>E{r$@9 z{qxJ!%)+`4w<*oqW&Zgmw|;vtvwII9;vqY3Tt7K@ zYTpwt)R$=FxO*S{+%FhC3)WpdHMckR#+KN^!?UZuCD*?{q`tfU)jfwAn~y%r%%9&m z6MOsIVJS7UcIon4OtrFtuI&1G)1DV$cafcYeZ#WFrq{m6e^S%dZ{Mw4z4M_H%!#iz zzIpJ_$w#l8&Rkk~_Kt6svkUUeXA9?FUA(YTb#^}U(g*Oh%@-Cf^*(*?wKcUrj&1n( EUo@|}%m4rY literal 1235 zcmZ9}+pp6E0LO74=Yg}BAYOO@VIfgt=IGjO?M8^&cJ12k(sf-gYe4O~w!3s|cj_;H0bP^H?_xC}iQCd`5<`gEvDc}Q0BRs>S( z5F*fAS?wXJZmSVc7f zA`BfKFeIp7FEDJEuF(VvvTzQ>Lyn+^bYLK9dZnhzC3K8N*_7lckeuXUpbH0lY@8`N zuGMj>9m-4FHt4h!DoNB6x>t(G1=(w+J22CB^B@&~S%xv135J{?Ocka&SW>N~GKyt5 z47yTFw`5!6qB&3Tbx3cuWp-2{bBl4#!PT$%6aDVpj`(IAaotS###L~>FAGe;HUG;^( zL+yQX@Xz0Vxq8n8-h1N12Z^0?pIz92ZR#(dS-0iXqq7Ugmd~K8_ut&~`}z4JzaBfZ zZ@L|!(08r7kFEav!KtYiUcMb$I=OlM^tJmxWOl6=&K@d0v-|CR-#It#9liL|!Ur#X zG!MOY^X@BqclGz*zkF%koz0UqspEbjhQG70XPF$79{O0XkxxF4m)k*gW^w-V2J6;U LWaiq=?VJAs;&HkO diff --git a/secrets/restic/reverseproxy/base-repo.age b/secrets/restic/reverseproxy/base-repo.age index 6a068c8d7159127d434cd8e63c5b654791151763..cfa70c25b66cee4e884de43393e3716123715aa2 100644 GIT binary patch literal 1095 zcmZY7zsuuv0LO6^aVUZgH=KGx5mdNXlQe0P;(3syX_6-Cw`tR+DYrB~nzT*&&F`jh z5JAP^K)gF}aX1%G4+S0Eba)(ub8$F$6n}t%2%Tcz^gTeXr&1ob(Xq zYq)zJraoHYjn?I6mIW=(ZFf3&w<+?@Y|b__D;9NI2`FbruH($Ii`brq0*cqFH>((H z)3)#()P0YY8__74@t%{j#fr&|y|QBh9QSIeYrAcaH5dy`l(||v4mSN)i`QiInuR3`qQ`R8E~y3~CPvsWwfaaC1i*_&Y`Hqg^^#Necw0FTw`;#ncHMKwA$@e1#*k9v@*y7R+@y&nBvM{W+!<+G3)^`J#7mz z#|Vry$`G4yS>qv5cA!KGyLILIyx1@h-Vp`HYryRM$_{qHtnN z1zDrjYBf2CZrkx;MvVP6Ac4231)3T}P?ZvTIF(|79+i`%3-HWoJbLxQ&%gfIe&oI4 z!}nF;!tCCqC%<{_^Xkdj75Gr)NgDa&V)6-y7$@ zy?*1e&bOE9pEs|aT)2DkZt|OZ``q_We>u_b;156c)nW06_u6NdK7IY%$KKbZ^w+WW w(((QGj;{Ru4!nE((NoIfPdwl#M`^2rDP0EnM6%E^DJTJ9N0IpJ_2^ zMM+Y17Hv(`n~?#QPR^uQ|9=+N6F~XYs@s9wZL3};=9t@=$S!LxoLH`6S{q>*GoD&f zCJ7F@)-+&X({WJvqb=(#RNUqQ;;ZaddyGup1IclU^hk@M`9%I2@J!+Op*#N>wlj!{|0`Put#1 zq^J14SDn>-OC)o$0cv$V&vIi`S~wwMGc!3Y@>3rrls?OEd#fyHXWWXx4Fj0lUUNf0 z_%`dq&KgC!#H5%YIAaU8n)%savShSw;#Z)aoYl~omAFd7({ur<<4G-bR-QYxR27_; zIyYR!d=4+qGmZMBR|Wju+&vrgU6vk&&w| z&}y|Bvo2DoiJXG=z-?psq;>>FqAC{_HK!|SO(Z~83eOiMfdIPp;M^nkcRsyw_2}gA z+;=a$^44z$&)&W8=kZ_1uU`BE_^WD3Te)Jsp)xp=d-gy1nU&86-mtOuxKm79ji_1&9J3sO9@7wDq!iUcT wH$Q)HV|nd^>dn3Lk6(H3;IUsnDcUOV diff --git a/secrets/restic/two/base-password.age b/secrets/restic/two/base-password.age index 45621404467ba26abbe37b1a697b7bd4f44f531a..9e3141f29bf2261efd70a653c743db243564ffa3 100644 GIT binary patch literal 1235 zcmZY5{mT<|003|_!Wtn+7FGs>etBTc?RLApSY)@`c6+(q?sm7k+sq!f+xNMb?RIy& zx7|^S5P|++BwA^u;PY4#$YW`sLg*7jP!D93^nj@JV1&Y>l#~wkW z6i2>cEBbJgK9XWC6pFqSwk$acdIBJ71xr-tBIQA0(TCaTtG3^=&KgSf*q!~k+SgSw^E2SpWT$x^5!OM1LT z>Ft^cWv2|yri@Z&?6)Y;4ZJ3)#e_Cd)PiKS^It245i#n4Wz#TLYxV2auvsWFQl{B) z;|h}MLm8d-igHKkS8HXSQ!ITD%5iL7Emuep_%=qSuw*>tm;{?AAdjecTES&`4A#;S zuS>67EGG)RF&*v$CZQ-?BasZuVbj-r7!;6373XYOQxRONXLPcGIz@-8g}8t&54w)i zZaBF}EZt2GDY4I&V>u=%aD#r9i{zSh!XM@<(n_roQyM@DV_i_FCcRN5P*JYsVqK{; zl^GS%DT8-8#%hafi?z*EACbgR&hzqiF($kC9%CMOSJ-sG&9*CVk0~t4Jx$SI3g77L|a73R#E{ zE6tH2QC-uBU~1MY`WzY(y`IqqQ+{p8j_pE>^^$g5<0P6QdBp0$j?_WXc8Vl{^S?<% z7Icvgv;C9~mji$M)E`ok3+Vf2-pNwAQGxcVS`#I9@pwrukSE95rzd9 zuVg%V6l1lzf+ul%kRh66L9zv+0v>45XxKnV&4hWJSBoxW#-w%_%An4IkU-6H!t5`-l;8rGV{XTOKZ1M z_Llb6=r-myQJeZ`pjaN&t# zb0_xRT({-ysxE!$=~Xl5p2wCxibnRQ=eBK$me1y{bD=|rJ}?)dd*QDpKA!$noZr{` z{>JGeySqz&WKO*`F?s&>pRv6gXXd6itl71LzWd_h!CUXl@4k2AZf@WF4{wj|Ed6x6 G_w7F^y2@Vw literal 1235 zcmZA0?T^z00LO8}$Lu61qKGDDXox0bbX%VVk*@2ybzRr4UDxe}xwhMFZP#{NyLFur zG`!)2_>c>pCY-^CXh<}OFCa#Oyb&%rMG}mP@gW)`2t-~GFeIFMzV!ZrPx8(0r?#5$ zrla@6u5Y-`X1Cu0hRH~LTh#N~@n$0h!$~~aYZk3Qh>D;g=4*%#4nuCl4@3iZL|t+~ zBR}8+yFxQMZec7H1G7{FDyWSqG!#`dY+%Cytqn0PumT^HF@|dllRAO4$||SItoNVR zHHPtS3Pz&MfYiYtN?M4HS#L^vFzQwNXd2Bc&2f zbn(EzRBaKUNG$|omR*HqBAQYsbViau%VoV%JqM+($ z(hzMy%PZznRAVGWu`Jq7PsLQwV=7kG9241?khfH)W_DRq!emHI5cm{TaLHn%UY0=z zX`-gbR|$!u5d`a!$xh9qc(M$mX}DCBvpt^jO73!v0gQ696&)z54MSj8M8bX*f$9y% z>qDrn!yPJ5;_W1_FmTy7OLjUD2@8CtRFm91o`Hc5uTiqE55ZHgQ_F8HSp3^KW(PQ8L?W)Wrj>fw`024PkW)2aU*@Um*K-! zK?VA~0YuOVss+Y!n&KLRRIZL8qnHkXOd3(@)N-w<6bYY*_8HRBT6wP22 z4l!^;&h6|nJI4qnpY@n{7|h#gZXbre>wLz1mGy&}*S0xP+6 zK#=tvL}jI}%^I~tWaZ+H*Y;hzv}P;c*!wwu_Up)*9rL&RymMyrp{GovclehRYWBpj zecR7>R$Q*XcV+eS9C_t|J=441ycI6LyZ-h&<IYPS&nH z@y4?{@eRE9_|faDc5^SBJN}ouZo_TsKU%o|t4ohe;1`3F6KjO~UVVC!tIQ3zT$(sL zgKoF4T)Y2M=DTBSg9E33SMDv&Jb3-f)$cTKD$}zsKl#NE z3wNAO)4S$3oZ5Z($lDKJoZRv{uyuZN`_( ssh-ed25519 qeMkwQ Bi93rI91LBDaaY/yPJDhvx2Xz4Sc3N/QHCuaSIvY4H4 -SEm6Su5gjKvSF6vyl/M80LMS2+JuzllJ9h8R9LWyaK0 --> piv-p256 ewCc3w AtUZadXsE0CuZPNJg+Rqbbh8cxna7+y2VGVa/lH/N7dh -O7V0wXiK0qncQ6bagJEgzWsUQ5i/K1nibxy97pmDgRc --> piv-p256 6CL/Pw A0WY0KqpmfB91+nNKnda1hudfI0OHxGi+AEBSTyoYBg9 -l9aGu0kEMfK5g99UADmGN7v9T4c9VPOB2ucmoN+Lry8 --> ssh-ed25519 I2EdxQ QcTXfmdoGtiGnnBsh8iA7BMhMGUdGz753VGTbnM81zg -HOAA19NC/kbQcpCvpBEhxZvIFQbJNlbW3SsC5D8er8A --> ssh-ed25519 J/iReg rCs+36Az9gPC0z0bZOkY64kqAQLTRJNIGDPeeAsLLQo -E6i/Tio41CtWvQpwPjgVN+RLyHUb2StBsT65LMnSgTM --> ssh-ed25519 GNhSGw 8iGHolR8qo6hHIVqLWtOGtrqQwk5lHT9hZA9MtW2vz4 -vPyPAHUkRWVRr1oZ8kzR5Tu2d6Q16hpjPajv5TxJEOU --> ssh-ed25519 eXMAtA l6mcO5XxwwQaTrfwd32ANLFma+GlwFbqlBNo+sI7/jo -VwjyfbUz//5bbDfCsTy9azFspvykY1+am2TDbajulJU --> ssh-ed25519 5hXocQ VGfJz+xp5kUTIGLNKE3p4bneECJ8lhETRxZoYq/MaX4 -apxhOfB0uEWMtEoT7oSfWkN66swG0XuN/eK1hWPd6p8 ---- hEoLlgb5t9ASMlVBOu4/QoBBRr5551YqDw5C3vQJ6C8 -5G3M&4sS='N]?ZU:|/?9XǣH~yRz0Q/7ʀwV+ɢ\(cޥ7>}'1Sg5g&ij> \ No newline at end of file +-> ssh-ed25519 qeMkwQ isv7irYOPM3ZFo+R7YmL9EoRYrFJfuLWkNR6r2lgjHE +PDf7UvNSxP5W+90YcVWpmC9VrsiwvfZeZIhMNtz9MwI +-> piv-p256 ewCc3w Ap4ULwesDkTo821rHLHT5QTgLq05Z03f4IyUZlnzyT42 +xseukJDoQ62Z9umifrcpkV2QUQl70haUbCQUQ0CZsD4 +-> piv-p256 6CL/Pw AqDUG43l33GpkKBLGSbVNavw6QP7SNtWJ/k1e5220Q20 +WlH4NlD9okKZa3CdKKGwI+1BZGRV5e7OhSCBFPmnMac +-> ssh-ed25519 I2EdxQ oT+2CGAukNKWiimsXi3jooqsrFBdKH+LK6v3+7gzeDA +0hnQjy6Hm8uGVRsRpNuesybm6xcUfwr/EIxXaQkQB34 +-> ssh-ed25519 J/iReg kyCVyzD4HOGYRqqWxG4nrx2kOkCRdm32c2B6tuOoNBQ +KXqT3v0xgIrbinTw64CrNQchqXJVdBniv6rMyGvRilg +-> ssh-ed25519 GNhSGw luLGKV0iAY6fRHUE0PuZhcizlsMbOwPK0ro/kLjPxl0 +PbkSGZgcKCuAbr4anDNaLcSOz/30P2+OCg6AUGd5Pt4 +-> ssh-ed25519 eXMAtA Gh4HkVu9m7LvBzcsixrCQjFTC/WS31kLQF8YimibI0E +XHap5pt+OwVvmzkTq3Ewq1VrTifwqwbQ5wv4g381ao8 +-> ssh-ed25519 5hXocQ RfYDwwRM2m4H/qRFVdkT4g/viTae2wGHlwgiuwyMDnc +slShejHqsSLqascg68uY4WAIe48VA5MMY2HYGJT8s6U +--- 8yO/uraAtXSuTHm5WQJg/QAHOww4GCWKDwOUOSHczJc +W`> 6bwP5b="]ބ*m!uʿq_ +(=ft6eXWITaUFt(nn4ȇ@5о=K>n*W(8{CY TKGIU) \ No newline at end of file diff --git a/secrets/restic/vaultwarden/base-password.age b/secrets/restic/vaultwarden/base-password.age index cb0999a2c6d6c9d0607f6cbc04a1feb92f737919..08fe6f76ff76df5e6a24cea39f040fcbc1194927 100644 GIT binary patch literal 1235 zcmZY7+pp6E0LO7&yf95fO*}+{RE>x^+3DJ~-I|Esb;4|;C44m1+p=cl(Mc>DqXW!jB0r*=l`vZ zdR}}4qZpw2SvlzggfI6xrF6omkpn>)sI^8dQ6_ukS^>()f<2%n;JDD%Fi)Y)m`tH= zh7v2Plq*9m8fP^xavWzGuJ^2nE{N5TZKw=F@_gw(7M5i}?k`Ix#bK0#n?cybD@;4! z%tDq-OvVTzbwWt(qzID>(wts_3bAai;I&~?sbt1;H5}fVl=?-7W3uB{Vb!vQzK_gj2K~5lwGB4#ulS$TU z8@Fzw%DO=L0D^ItMO*cj=Z-yvq}jXHip%8`By9h+gUlCAP> zy(A-O(m^sf3kq6IZ_$LyLte5svAm3F`SPur+AI++0pyt!Z&z!4V2E_i&-E#8=r}wn znrc6aMn#xIFsk6^(^4ZJn=VLdJ;>;oVWnwpx@Dv8*gyp)7tnRPLP|ZE;gU#)_1S+K z`FH)O-n8T@V2v)4!h)XHV=i zmOh$2cVopj?_RWb6;G@>MjczSdH>A9J>j+2q3n%F|LtH#-t<`IhiAr#nVHW|A6?cH z*jK;U_EP`;OKWyq-{imX;PU6D?lzv-@%8&Z?FW#D*1im>XRhr=)9)QCTy96P$$^qbQI-4kn_8h@2BKC`KZI;{(SiFB)(0@}&3|eDeL}rxz&@~7_KrCMOLasE9OSD7gND>8D$M@PAgOqK}qFkdt zwg?=l!wojurSVETTVrAtHxM{ypu&x;47!L0F?;svu;!U{4l#>2J+7 zgMVk8MzqSB3fPC~Q6KJvX#&r9%zrGDEG4UdSr~{64YQ+#03tD(Lw^ID5;FehEswiDMGpcr&4fVY=Ef$ z&yN3o2wmw2^f)di6#2j(Sj#9iZ4B;XnHzuVnSWh}2BmjNOko^ky zzc!@PbW}b*=+%ly6_N&mBooC^PXMhdpYlAemDk!PoJbU}-&14Rlm=Svge}Vi z+eR_BLMuFMa!#dF6OF(mDV&SINkN^EsaPVBhLARaziT41ImVSQK37Q8;f| zK(eNDt*{J|`5|t^E=;peZ$YlKt6y!n`qt9ZryeN&zVRqCH@LFq;Mxam(t&A_Pu diff --git a/secrets/restic/vaultwarden/base-repo.age b/secrets/restic/vaultwarden/base-repo.age index 885f483ffaf2b190989cecdf8ec14390d4a1e664..47628a26d5dc5d4c5eca11184fb5e9323f1b2781 100644 GIT binary patch literal 1093 zcmZ9|&CA<#003Y?JQ&<*9%Oi^$Y2s{lD26Q#6glK%~z8&Y1*Xou;!!rX!2{mn)a=r zC|)Ki(+MgJ1|o>Ns0W=oCL&{pG1*}ZR1_3E&dYX`4fW{z7d-GR!k`@huRO@>cnzLg zmm$(%Q2S)7s-kw_cW@k|T9aa@DchEqmdvagCG#Zh`y0eoafxxeVk99`4qGpTs?f>7 zG_#eV1@VrjVwtZr14U^BdTBREMZkt(GH|SmcH&UHM&{dsRmA_aJZ{=~2k*CnE$el+ zExKZ#|%_TRb3>Jey zw8bjMuQbQVH!S#o?iu7vZU~2LI~ZNv6C`??UBtYrNm?x+2L-|m4!*TKo2j99nRm4TcuoU z6IEXE)o94fbD*0W1Ig-5PacL@j2sMDL-Z7aDMA!V_Y$Y_tK1&5U}!g#wbnQ8WZlg3 z9U^hrY~%_ZmKd8JTpiXVBsPMuRl8(90UT!Hp=Je?JQr!)#ZA^CxT(E_xpL2w1vfTJ zz6Sxzt8G6JHEC~}gobbh)lCzQ3OpuX>A67im2J(NLWIYMwV@iB!)+_VOt+E(f*1QJ z*yXy2dbyr1;u0*DdCZ6j=cg=K7arP!6b4&0qdk_>E42f2}NkdLNoaS_7laUayw+t=! z!AvMkK~cjgy~#E~9cvV+>4VIPF-$fJQ6$FJVGWP$wKr}>x`d440yV^9$(!7~XUuuP zZpXl6Y1HshMk^eqESx`t5GrE>wR;;3LLY{_dCa!r4~?NO~tcRqVY+RM^Xs2 z+wImAbgUBTuX$;s&qXU$#-?ML_Lg5bohEV6q_f!hb9WYs4)n#*Z(e3Tx^wsSw|{*0 zjV~WLcKXuE6Q5sw=kzP*@3A+>gZH1mO*}mQVYz!y==aV&c=9^+*y_lOj|1}O6YRBP z$<^O4|9$6^{;4O literal 1093 zcmZY5yUXJQ003~|YQ%CRqf*<^xz#sUzyYI?6D)PsReK2fB zz~Cvcs=|TqAvivofZFc37Fux6(lX_!3rlh1PMgjdRim||FejhFqHna#yd)r54%#gt4`KtXRRTQCi=?CNI98FaX*&oc1CU> znUMf(>jomTK*#jHYZamHU3clrUFIc(ZqgJg#-5(Trm*d~dlxxm?j2=t)i_uzDSB&W z_OTO;n1>gE6xdW1GI_U|kR9n($`r4)0uYfriIee+3(~FA1gTf@v(#SbElkWgf0r)y znTClGjiCCy+Hx2vegGcUJD!^f)jlf`9X=kdqS0CnUa!YI%~L8-=D32uR_)3hfP0Wa zPivN!F-qWhE36DMp2IpaQ5dX}h&hWIJQOsGNW52LSE0(bAP-wjzRSsyTF>d_f?qh> zK4PYt*3Vf??s`S1uzEf`j?@-_w1W|8TDM+7hj5Hg=#mTdWQFjv#>$tYkut_RvfCXh z=043g#c4eSQy;<2W(0KvpVZ?n>ZZ`dA#8kmPznNV*POr1HQNhgl>_2T6{1m#iHk&vpI225WE5Z~aH=OA@-Y~mw5)aM7mBDc5&#D!T15cjf%n<%hhh zWBrdeUcTFXaO;^LsVAO1LH$6v^1$7XFCNS#??&~@^4(KXWtaJ{{d?xb{GHv diff --git a/secrets/reverseproxy/.gitkeep b/secrets/reverseproxy/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/secrets/vaultwarden/env.age b/secrets/vaultwarden/env.age index a74fe04249dc1ea926ea96c8339f14b88d3441e6..3c003b8664de790925c5876dd5d4cccc77de58e5 100644 GIT binary patch literal 2951 zcmZY6`9IW&1IKY4I&>)AZO5opF=_6LY{r=TKFnuEDdwIrml<A# zDu1~QjzYn3AVG|s&9H%#1eDPn6~39StK3pIG(@A)g6UC3hD`Pn! zWP?YP6`@h#LRmUKFWSPxJ1u~SV+$3-M0%8l3`2o61U6TNqrhw?=6^M#+UjqFqtGC^ zjUYwXKzN6PW`i5hFtggC)93&zf+8h~)mVa7Ap{&`2iPgL5_PdEGS9^Iz;OsMaI91q zW1tZQ6t>c#atbLdD32N~0Dw>tmuw(7pkY+5215NG3r%1^IR9EU30}kuSJ>GGnMDVs ztKlY;B$|y2LlXpiBEqKN;P5mG03$h^F&+ja9i~!S0UVsf7s?!F5gLq*f?KH!8QLxj zm&QsRU>1hV=D@;;Kd(rI6J>TT2&JX50URI$$Ot{4!AI%IG6Rf9h?T}zL`YRQ25f?w zIXndsql0=tC}y-6qOe92XlOd!29^mV8jO`qHY?2Gd;v!S*6JyERX8C=6ZW%4gQ$6O zB}k8DsmN?6%go2=0D@MjFdE1>z-l%s)NnD?5ejjt7-ol3ViqYqVlW7SBrM#CrP7t* zTD#sLB&#@~CJl`(%pv?gO)g^MWAPxRR4o)lMI#kzbT|`Wn`1-FY=s(Zz+#b3u-qoqg_+?PgF%aS z;yuDtBm&kTRcas{hJ|jVimehN(H;{1j{@jLAi40OM2nNRJ z#Zep>o~?4=SwgE7fC900r&EcJX7H2_rz{#TU~2_TjLzm^Q&8b>GKAn{I7xIa42Mz4 zDGDuy10{3727*nEwu&_x9nzr{M|=4D`-8$&YQ7c87xM`;IfWC$4FMBk@u5789LIoB z9SS6e%O|lgI3tP3_4uMGnl6MW4wR+@j^9s;SmPHp^KnXH^sp*uqDQ}G!{Q;>^>gXC zcO^Zb8-G-1oLqEsO{E)l(%x(PlhES}mm0&&rI~q%D_?yLGp7j#NXeH^5zN)O2xYP{yW%)D%3x^l1#@)lhLOm0PhG<4oWhk;p1XKWyB;ZaI=k%D zZ16z$lUhqcKW$FJN#mx0!slJkl4nJ}J5rl_f}4}Jt@}Ru^ts3RBx0D!;ND>B4!?a^;Psh z69Klrvhn2AN?Kv1)HB}uLyHKAPn7p1je1=h5F2;t&xNn2z0MGvQFU1q^B6l<-CKm(XF5QcllPFTgvjiI64eFTyrVn z+0}PxiQUr`MXbW7qqE(NMz3wQ>(z;bgFl?C;G%n}!{KK;!W>xY&O;N+* zo#hj98uEX5P0hZ25-o%-U@m;Sd}O!p>)Jl8ebVIJ#cl-)&plSS+49T{zb>mci?WZQ z#*J0)DaQ^jdqzFmVS3~K&X)cngBSBcduxKAZ8+JK4(kh4t>tukKR%S!Q`z~$>*9%5 zb?F=W{`P&d(g#rOSeKp(CuzPmYN96pnYza7#mi3#$GJtP^D!$P&LSVsWkuFp`N*l? zXk}shuj3^bnog8$KC{)svpxsX&{DxZg3reM5vtCp?EZb&-H&?!x>*oxxpn5n$yPU~ zH&@$Ko*%ni^4B?FEfYE?cF6JmRzu1HjdM_)ZiG*IkUANhI;ZMV@!8~kgNX5Y%L80k z$H}CUFH3=eg5O_-vtuA}xvj?*`$VFQw}izv%m>J=bv?s9r-$qfdtU9VJ65n@6*PV? zAl&lpc=v@UfPK0DX#4Kv+P(iId(IU9I%49yK0YpD32lE?muROw#y@;d*Du!L@lxZv z6zWw?(QRku>B7ac-LK6ZSLWlB{WL(cyE~V?W#{M8$kC}7WJgHBwD!S~bpewK;&rM} zo@ni~ZLg~|7gMgSlYFYl`#NUd^jT?~EKP>|DJ+=-=e*eX;0c9G|y z@tO<)?J5X~_uRGKAFnILca(l`sP&_zcW5&vR z=L2f0p26lGnhbegfwrbyqL^P8UeC>K?A(;OGULxmMR(3wTfC|bHDW&X&zs@hK8@oY z{qY^uk+0S)hiuWyIf%k>%r{-ZQyYHkvqFMfz8)kaX4fYct=5wkIU?@2((mu-e>v!> ztAmXg{m4ztbN9WdOlQG&xMrm;n@DQ(61lF+4?G^%Ae$TZD&rtO;m;*OHK`wiW|>e^ z#3|9PiI9Y(4fgFT^28f*aCbpd7F9Qu9Z9w|!4t0E_69(4YuCHhPM>z=*wIcp#aR+) zd7o9@T~_wEcMmW>kHaj)Ja*FkzGV&RnB4(X3m)ms+(lch-rL&lb{&!IH}U<&M=x_{ z2rjSwSh;=CP$~r#)vF!|CLsH|JytYgt72b#9M9qk*ZTqHhQhu)Kdw? z6HjHfJ?Pmte`?(i?@NYOkF+}1ePNqzyrt^wgAP}^qoOT)p}mD48nmtA`Hi)8^PSZ| zA|ikHUD*upIRslhXG7_^A;+~F(t_U{`%-T7(}ZXNMgde4`k8ZEgfe9y_*?b7l1eU5$CzPdv*56rrLs>h{oA32Ko-9Ts)rw|B^Hq zwP1QEzkoQLg-(@?eD{CfN}3PX>KnC)hnWN4mcX%fPNH(^<4>+T9N%h1Wkz-gXz`AN znwobr`X}czZoeP1)|Ol!N$-KcZmx74m~&Qs7BN0*@X~|kCvX>Iz5g(gbt}GSu5bIm zmIZAmqAe44d(+9>Te`kIn+%?wsU;kOT0mQ*wflhK=B(O+^hKjivZ=A|OM zZg%ws_3n7~qv4p;+%<0FCD+4RA?En1l3a3`Nci~=SPQ7QNYcPG)*PoG<-bfGGwL?_ zeS%2yl8hxGUOwTK*}{ucYg*VOh8C{tDE_kR(fNfJn$uLI>~d#nVdQd^#9dhZ_ok1F zYA>Q}hSVMBWxz<;2k?)36StqBTU^IA=i`T)6u)^3*7e>2e4Ci+dz+{26r~J|;l`GB SfNUd{cX`rw_p8VIm;MLO4k*n4 literal 2951 zcmZ9~c{~%0;|K6W(Nei8U0RA#&Dv>8PwZl2w{5mb+Gcidb|2wUI?;hrq$uR6M2bQw z=^%*?6i=Z@hn^Br>H7Bj^Vh$hf8MXx=kpOr#2~R+q_f5wWE!=ae*f=;^$ReN=e2swwmk?PzT!f8HAdtb#0E6iNw0M~b6c0rpfnqaG z1TzCMNCXtl(F;W=rkE7Zj(|$gB%uY*X0hYJ;8g?~Qzez#tTwKN9!CYBVWDx%2$Vq} zj}6d5t)bFbA(DudnaKn^nS>1!Y2{!QP7$vaaZ&%zLgL6^`ftmKgm7sL9^0Ui(QRx4 zkw{r(FiVwk7LgUNHKL4qC&5`Y0Eifu%!hL0h^g=@rGgVu@(jpuf>vM$#86}TENG%1oSm{2q#R!2tbN;bTR~v#hJ85 zH55&Nu-E{Cm@MT>HC6s;D2giU>Q>^ z0qQ03G6mbB;sqEe0eY6g09MdKr~;x59U_yX`SC&v@{h1%dr}a zFc=>ji3D?yECg9aW5-393`~TYhyoxG29_b5gpujsaTJ0sjHJaO88R&|4h5r_AzY@8 zfF+2iN-_<~_)`;eDHsC=D1+0)GNctoVPN$FH8Nhn7HZIRW2{%ka}^At&P?Y9;1paFUB!cF`E2+qadAUZaN7V?_Y1%$SWV-MX3TxbFUD({%tdr9-u$7g5Hw_-> z$!qc3Bi7|4dmi@o_bI%vepy+q#NqW(h&+P0EXK!yT{P$f@ZR9og@4f_H-2Dz6fdPV zJUz~HGW6BVpXz>pZA;;H26#BBW)-pLHJtv#t0wqB^xg)s0waiK{nQ`2=x^|c4)EZ>(FNA; z9=RT=?*k8ZZBBLH@;*HTKi-lZ>=X{Go%751rg^n<^VqegvP<=j^T+1pD)Xqa%MmC{Eg9C}&y|fv|62P(+sNk~BX4S^%J%Mk3>r_geq5OneXnAu z4%T7&N@?kBa(}w5lB*h1_)VQd5{@@`Ri_uGxL1EA@B(!WaoSDg;-cu*ynCBl3`hNE zz2BT=CCw-!?QhTBdav%tiS{mE*htBT<+VcSnd?XVR9qk5I~LG*R<*vj_UpCi674le zv%oJVV&M|y=^p>W<|_cC$HshX*Tc0@oEwc_kMwq{q90+^@7As_Cex!6r;z>vG@kca zyrggGGrK|*VcVV8^PS5uBbB=Rz*`HDDHGl!&$lHPw6VUvF@KouDjK=i7U@-YH?Mmr zZpRXMjkm2z>A;o_q)tpel}G3}TMPa+^Vy!HEtYxt`N&x<%n6^t^Q%&MEx`*qTw;6M zzgdzVb$JpG*M0od<<@`-b{PCm|DMc)$S24l(|~FKkUC^TE-I^{;ia%2c&AX;(Xsd9iQ2 zswWSQAB>w5LH=@Vus#0zJV4*&yP)Cj`8JU>>h`VYzZwF$fzuP+M$;VX{ZqD1OC{y? zd%ZlikobIQR!Gp9cBU*7t$i{+52oyCdR%|9O$l-c_FvbTUhRnoP1%~{{+fyD#iRMz zg>cepLK6>j@~#JpSd^S|XtUQjzK?&Biyf{01pRv3f}%~=n(w&e5*Jq{ej&F$PO)#S zx{};R^GjMb*LIisc}SG=Y>{x>nlZolkZ*Ixo7JthOk8nR`rf&fhi-52PR#>;slFK0 zr)!`6M$l9@fP1nJCcNJ{{5EH~)hjvS?Cosr!KAbxm#)v^o%og4$aQTUC&H%f5udSd zeNQ=i+380l<>;s9<$I5r!J9T2kb+u&xSZTk&c}8gIsr+3Ia_>*a3m^j}&j&t( zzFnL@efRy|y4l04o5rRVo&GKpO^QkjCKm3NWktmZOH#H`V`nVdKxNj+$w?T^cj1(? zzhd7^mL$Rv2OKoWOZ^=o9v#Qm{&IMw|Fq%1O2*`XqvK1j7V`H^dV8m#dIaG%Q0nMK zdkd6J7%k8mJJd}3?WdAwHC^3QwWX~T!mYDFs|=6%d#t^b>1U*(uYlQ3<0G!`yu#9| z>>ZrzU1%8qW;uqE-t=VF%_G@Bm<*eKm8g@cpTzZpYPJ Date: Sun, 27 Jul 2025 21:19:10 +0200 Subject: [PATCH 29/77] 144 -> 44 --- hosts/vm/mediakiwi/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/vm/mediakiwi/default.nix b/hosts/vm/mediakiwi/default.nix index 0da9410..d89b14d 100644 --- a/hosts/vm/mediakiwi/default.nix +++ b/hosts/vm/mediakiwi/default.nix @@ -16,7 +16,7 @@ enable = true; networking = { - id = "144"; + id = "44"; srvNat.enable = true; }; From b732e97b68cd787c2f6ad23fdbcea16974bdab82 Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Tue, 24 Feb 2026 12:54:40 +0100 Subject: [PATCH 30/77] Trying to reencrypt mediawiki-ldap --- secrets/mediakiwi/mediawiki-ldap.age | Bin 1651 -> 2410 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/secrets/mediakiwi/mediawiki-ldap.age b/secrets/mediakiwi/mediawiki-ldap.age index 889f7285e6eb0899aaec6896cfad37e4c0a853e3..b28cc2b9c6e6835e10f87aa2f2ed5ece22d96e12 100644 GIT binary patch literal 2410 zcmZXU{nyik8HN$%hy|R0Q>O>4It7idY15`n6~%nCP1>eS(k4xNrYuR*q-olwX__=~ z2RY8YjyXQ2+wr&|_<=fgPClFthu~zw*_aM>z7B`Z4N-B5Lx(cO*Rdb`Vfq(b&wZc! zdL&I9Q@e`oTTZLjy{u`)SE!8nJR0o@` z&?6*IZblfyBXYT7IbZ9y)ErPNXR&t5u~^$HL{mn|G%?JRx!#~4#u|Kl6v!367y^+I zWOiD81ayien!s&EK9m1cwxHy&!@qS_INPA{r0KklIRB z@{w8@>JfFe1nMN{4(uQq5iHxSA_P)Rw4$JZ=XntiT)G~`du}V2DWP#A4YdFP4RQH0 z-)C{SB(;09%8N#G(s3j z=M))^THQXVL@-iS;uYB&Ax;+0@r4c*Ee{G^N^^L%+_VYb=ZXYa13lU{>NL+J{g5J6 z@_sFXGsidKl1v<0{w*X4^YMFXrI3yibWNTlhWs7<_>xH}3WU7#}O0iBiDd$ieb~r^z zmGX8=RTG60AYhUTnI4+N;-Ny6qB}HEOiOID>)729HXsDKowmA6R)BIz0jZ^Y*GzcI zS0xi^)Ir0;XutqYiWBM%F_H5#-G4F(HXpyBJo z7-kf8#bA0pK5pR7zzU!SV{=X!90~qcvcbC8Q`oRof?yHtleohqg+#ST%7AH=Gj6Sw z2w=kPRdLq@U~Ncv@sQsnn=(v_eG>6~&m-DRSBy%zk)6@1i5!$jlnpISbc#|O{c@8s zM+yQOZtA_7%HnvL6Dc;rf&o6r*tBhlBGep8$l$ou2&I<>mE$iUj^|T7Nap>1m2Q?& zty+fgm}sNjNJmnx)XSQ(+wPiy30PmrMrzfRiiTT_ILsEPLLy$n8i`t4SMn&HkR?tm z3s!}S%5*Q|1MQ$xRYDxv7eqa9ijWlK4b<*28Br{0L|#ZX3Sh>MH&E6}^|g`gg~rCl z!b9CIBinAtvKRjjcP4Al*-gD7UW}=ZzTh5uozVXS9?Q>SQ zZv5k#a_qp4yUi7Y)oYJ6C%^fxhZg;Lq)d6>?C@Dj7Dp#{`jZFy-+K1oFKhcBf8c^6 zTfcVoscOng>8bj89AUte&^rftG`g*gWf?z;8FtKNNJ*4Uz*+jeg{dFra~ zpY`6V;;nBlW47#J+Te`Or#@7n?!B#f{!I0SyWaWD<|nL)j~+R+_x5+RS6(}D>A!D1 z`uOt;G6$}n+h%|8;TgZeFJ2m+cc4G*_IXcC()-t6x%S}le>^{U{tkX!Co4X7#kuCR z_xG${QT@haQy!hQ`;yg%=A61?`9f-~`toVLkGGwA;oWzwI`Y>KmmEg-A3c2WrHS_@ zru~PXx$nLg$F6}snAou|nE%o})UtIePe1rsY4@Q^ix<3=m2zu0Tt4sS8*X~}&ZE1b zVPd|$_!a$)8H4?3SG#2~y?Z-$_Ttk&N!-70Y|Z$O!&ht{U$&3k^4R$5gC9)O zcfw0H3|B51zh;Jf#}ng`r6+8jO+5IfzA84!A}8JW3rxGAV6EYPe$vUeJvnvd7WSu- z_1uZ`W}o}o375Njs?Qufk2-$&%p>w_YW<;Er(N{oVm@+Naz?E5vjeN^zumi;czZJT zqWo}s*)zxdY&ySa%FQpG|LNGDR(*WZ;VZ?5u79-l$lKP&&6_6fgD;zT&vnO^C)S^H zdUw^<@9o|2yVKU8%Hmzu?0BW}mw)E}u;Op`F1U6fyCD1Q^~X+F@bT2;vvYH;r{3LZ iK7H|XQ(_yYe|O?=5&&l8U}(XdJI>s-&V}-cNVjU+|gl zBf-!*I!B3>e2Eis&k7;~NDV68bLwJ|)zK{w3^yQZDQ=E3vf3^Nd23m6D^$3E1odva z-&0NEh=j$xco7#;fCm#Rr9_wp-Ks(~2wJNk)j*W(1%{_M+#q{Xptss2`_Q7z9TBUp zFkcl?BK5PD=OkSof{kigwo!UnZOMClW+%mlo02kLC3rU#WiW+`=}Y<~=#P03jA=;V zfE)`M2$g_qZB7cOO-TnKacDG zbkGtB$bP%q&4~_&3@Hj__a;+lh6sd7Ck$A9B~Q>MsH&LFuEpV0AZW<>@vO0&Efj27 zR!)Fr0gYr?4khA1i(o?nk$YQoLyi_Dq!uU~!Ly1ed0+_6@^pmO>KRT%2r*!+mSW+Y zMEhZ9hVVhMs_bE+<_ri1>;=y3b&J+a0m8~aKE^W;l{Jd_ysHIcd0#AOxL5N498NRp zkOJ9QnsPLOAx{A^LZ!UK=0kQQ6M#vwXfE-ViWnljIQ#F-QYLqRat8biRbmpPo@xkV zUcVVMAq11eqo6ScWJ8H~vM5?C_iCu0B~V$NC@ZEM=M}kRG?~^%X^@Dda@As5(58G5 z!38;U8I;O!CWWU|8K1czDNaLN4>7XMUI|(d1d8V%E1ry?ptcBdr3z}zWCZ0utNr&M zY0_sYS=2><&lfpXVy!8s)kp{8N;DNMlVZgb%W*-_SPd%9+>l7)1n7bs z(PA=Fc1tF6(!=IkT2fIVj8({F)xd?)h`|0|Hkc&^Dx}sz`brtdnUf6W$Qt;vSEdxw zz{m0tCq}wraIBiMW+@4WG7%MHsWiY=doEw04GwG27B^xJ35E0i6391M?M+E8i?L3bvY7ViFR%D(DW{p9DDl|Iqz>C|2| z8N+WP({{8za;;-S;r)#h?jHOzGOY3Q{eu_s*$!gxXxm9o+{E6oclU)wyy4c+u`9E z!nBX3pJg80Sid3>?wWY!Q*qtkKW4Nj{W(p~w;PKGdr#R5zg^jT%=1EJ|9ei$g_<=D zvj!$$nwjnO<6li3n7(?U5~~?HPF&u3Vb1o?+NXZM!*}%T+*_MgZQkFysCDvHm`2Op zU9WXDIr~S3V)N_kKYHVZrRGU17LNWf5AB}=BS~z<@A{^t4%fEd-`Fi)X?yjH^W9s$ zQ^uAbn4H_TVAV5gN$Nmcx$^N#*T&uMzP4oENX^Ks^IP_G&V9HyIBj{&_N}M3tPjD5 zHYmSrpQ=ANfIjioUmchB{YGCq_zsA@Gp6$|XYNSrf8D)r=IOKDum7|DknvZ$7gW5; zDkJ0QX&&zDN<4UXXw7=f^~KS}s;9qsrlZe%1idwJ&EdDwnz3Dzo_tq=r@k2OJ!ZTZ zJ2elOF_%ArbbK9IUMmeXzVzHJ)O$wWJ+4l7-SyS6N9KQt9r=*VJbtmd=jKn(&l-tG zx<5r4(SzjLF*AQZOs)0?wy18ePxsHi_{TXtTB{LRbYgJm^ShCov2?cn?^ii{+F0B3 EFFbjTvH$=8 From 6775aa673a4aeb62adcec2358a4d6807da73532f Mon Sep 17 00:00:00 2001 From: Lyes Saadi Date: Tue, 24 Feb 2026 13:11:55 +0100 Subject: [PATCH 31/77] Trying to reencrypt mediawiki-admin-passwd --- secrets/mediakiwi/mediawiki-admin-passwd.age | 50 +++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/secrets/mediakiwi/mediawiki-admin-passwd.age b/secrets/mediakiwi/mediawiki-admin-passwd.age index b9b5104..644c203 100644 --- a/secrets/mediakiwi/mediawiki-admin-passwd.age +++ b/secrets/mediakiwi/mediawiki-admin-passwd.age @@ -1,19 +1,33 @@ age-encryption.org/v1 --> ssh-ed25519 vZ8Vgw yOcz+ZAlhf4KeSb3tF3q5Wg1uHaRQ93pY9kI0MnoqBM -FwnjEr4YlyFqWkSnpT7ui+GbMLfLsyjibnhr850ha8E --> piv-p256 ewCc3w Agp5uCjfrNO/MEW6QDaWXE9/3XXfJ/wRGnPaNQsxH5kU -lV1Rcd2Yrdxwnb66FLKnkpz/UXdwnZG2p2vS+rS7QOA --> piv-p256 6CL/Pw ArqCgkL0Fyd8BsIDA9G1J4lTw3RIWKYUxksntvY5gqCh -A9NrcMolzB5WhjRjkHh29mG8u7wWsTBwtIYG55WHgoM --> ssh-ed25519 I2EdxQ DspOhVTHYSR8NmaoMABxWBJwrPU6l5/Shg7MFO5upWM -86IDfoSZsHlVAeik5mKsocKX8fgbxyxkDTr/SxE7uBI --> ssh-ed25519 J/iReg W7CxjkYTB7uzsMWIwVIGCg2pH/v69UFTbp2J5ssrjQc -iIAE4tHVTijDXVo6vBUg046NnpjH61gdIQ5eViqTSrk --> ssh-ed25519 GNhSGw ghUAibPQVo2//mzSBt33LIczzJIQrU9Z9+vuSM0kuFQ -baLeilviulJJT21mYRdCpTuFpEui9B/QbVNBJIMm2l4 --> ssh-ed25519 eXMAtA U419VRp3+uohf05v/rqQ7KtrQYDAt4ShTnssbiQOow0 -VAQXQH8y4h/xe3AeEjAIADI4uegXmlGdJCz0bW5uh38 --> ssh-ed25519 5hXocQ b2nNj07kAm7YFBNo60GSUSsXCLGTv5RhXEnY0MBBr2Q -ApMxBMg1ts0Fism0NBplcy8qTISlKJMNI/sbNY/JFzE ---- 9rdMPy0JgtUUr/GhQ80smNKyAVFmkf/Y+UOYHPehlEg -KtuJHg ~&:&h=R~z}NX \ No newline at end of file +-> ssh-ed25519 vZ8Vgw cJhdOIkMufEIHU+LqOAs4/KTxOiiBL1Knl8ChkApajM +lSJiqVLy8KS0+pD0MSDgtD2IdMD7toVof4u8zFbc534 +-> piv-p256 ewCc3w A4oqsiewlX50Psnk7HT3nRGHd3+pdgb21kN8Zk1hPwKa +GAsqMgKtKRCSroHria5qAWSY8XqOUmHDMRCXdLOIJdQ +-> piv-p256 6CL/Pw A67ODihOF1IhvLWhUsIWAQVmhO/XTJ6GRznS4GkJwxOO +3zriXUAIS3RLhVDmeCzFka4LerN3/fgIJRyKmJatIFg +-> ssh-ed25519 eOAUSg px/iV0OQ9ZtNyNJsPIWdEbmemBKaXqfcD4Ew85HcVAo +7LMHrNse0MjlLNlAwbMexIcSRjK1vDkLSoEfJAtRmLU +-> ssh-rsa REaZBA +ewC5fAfzqr04uPsw9l3E8PZKoKIDACP1U5lEnL2++04zv7w9GL0eEHa693leB9z0 +OJNhq2FHU+NH2IofoLm/k46ma3XVWaCNExYiSaRQWo7Cm6fON2F+K32tJaxeFc9n +xCE7E5YM75e1U9LOqSPdfoeuWUg5iLDhg4uauMPagCgKA5Dd0ndClZyNWYK2C7NQ +V6MhczLs6cxrFEOQoaudlXnj7TDAiCdVLFmvlas56+QqVA2SXfM+xha3flfkZvR4 +rdcUY/y0ZBoLaZF+K0f2gcqDlL8VgvYFizIPTRAZYEKdMytSJbxv4LFb64E85Lwe +JFWPCp1lBTVhDFeulN7XBYYOCacw1Pyj+n3at2GIPEZwhnl6++NtBuiWj+g3Cnfr +BomwuFlcZmykI3p4qzPlwcHaAeY4UhzciNo+frDMKCUhUmczBoRMK45oa8nv5AC3 +z2h8OZGtKNpg55/FZJ9NONr/GA3xR74iB6chpANx8h6e8QDnUo9FZyl+qZcQ/Ghr + +-> ssh-ed25519 J/iReg IFdFOpdEUjMHdunUN7vfJ09lkDLA/SPOQY4b8UjT0S8 +qQEZnxKcLpbhc3u1zE3QCI8X/XTCRuWOwPsOL2SzdzI +-> ssh-ed25519 GNhSGw 6aFCUFK8BqLZrP5m1qGYxdRihpaoJyueHJa/00T/ZHM +Jfa1/KfD1JCYdIsdF6h+FjV3yozWfiscNWv2Krkdjz8 +-> ssh-ed25519 eXMAtA sFwZS3oq80e8CfDruygKRW1oP5aGfpHqeOrqEcoRL0o +lYaTFhvGXqVnd7WHFZXe8FZ9pWtk6dL9M6h3DC3Eov4 +-> ssh-ed25519 5hXocQ 4eznATzeJliAZkVeR69DD2Gr8YKWcarlSSOEPjk9Cxg +Rd6iAIj7jCs7/sDisd9ErKG63bA6jW07XqY4MdKBGjA +-> ssh-ed25519 bRHVVA YSXkgUVhRpXYbbeVpsdLZzncNIysYNrLvc5q3JTaLWI +g9xoh/G4NnKmjXPuLy5flQCjVYGbkAR5RHOqnhi95/U +-> ssh-ed25519 HgW9eA 4uEzjQxKg8KIXPGyMRdXdJb4BPC5ZTRok9tAXa6AwwQ +BXnFdGZlCjYakDN81w1fxF2P94b2ZAZAhlBsbM3I3PA +--- ragIvYUEISd+PwMW5KyGj0r7HOZQiHIDbY6vHewuZ1I +KJ׀ % Xplvt!q^aܠt|"&7 \ No newline at end of file From b0250a75485a888ce0fe348b0fe7adb786bbdaaf Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Tue, 24 Feb 2026 19:41:52 +0100 Subject: [PATCH 32/77] feat: OpenID NoteKfet pour mediawiki --- hosts/vm/mediakiwi/default.nix | 2 +- hosts/vm/mediakiwi/mediawiki.nix | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/hosts/vm/mediakiwi/default.nix b/hosts/vm/mediakiwi/default.nix index d89b14d..bba9007 100644 --- a/hosts/vm/mediakiwi/default.nix +++ b/hosts/vm/mediakiwi/default.nix @@ -16,7 +16,7 @@ enable = true; networking = { - id = "44"; + id = 144; srvNat.enable = true; }; diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 79100f8..f8d5f54 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -16,6 +16,11 @@ in owner = "mediawiki"; }; + age.secrets.mediawiki-openid = { + file = ../../../secrets/mediakiwi/mediawiki-openid.age; + owner = "mediawiki"; + }; + environment.systemPackages = with pkgs; [ imagemagick @@ -101,6 +106,8 @@ in $wgPluggableAuth_EnableLocalLogin = true; $LDAPAuthentication2AllowLocalLogin = true; $LDAPProviderDomainConfigs = "${config.age.secrets.mediawiki-ldap.path}"; + # On importe les secrets pour l'OpenID. + require '${config.age.secrets.mediawiki-openid.path}'; $wgPluggableAuth_Config = [ "Compte Crans" => [ 'plugin' => 'LDAPAuthentication2', @@ -108,9 +115,14 @@ in 'domain' => 'crans' ] ], - # "Note BDE" => [ - # 'plugin' => 'OpenIDConnect', - # ] + "Note BDE" => [ + 'plugin' => 'OpenIDConnect', + 'data' => [ + 'providerURL' => 'https://note.crans.org/o/', + 'clientID' => $SecretAgeOpenIDClientID, + 'clientsecret' => $SecretAgeOpenIDClientSecret, + ] + ] ]; # Theme From 0283224916350ab8826f1bdcc44e440074f64415 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Tue, 24 Feb 2026 19:53:12 +0100 Subject: [PATCH 33/77] =?UTF-8?q?fix:=20r=C3=A9cupp=C3=A9ration=20des=20ch?= =?UTF-8?q?angements=20sauvages=20de=20la=20VM=20et=20ajout=20secret?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hosts/vm/mediakiwi/mediawiki.nix | 3 ++- secrets/mediakiwi/mediawiki-openid.age | Bin 0 -> 1971 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 secrets/mediakiwi/mediawiki-openid.age diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index f8d5f54..7a0293d 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -48,7 +48,7 @@ in name = "Wiki Crans"; - nginx.hostName = "mediakiwi.crans.org"; + nginx.hostName = "mediawiki.crans.org"; webserver = "nginx"; @@ -180,6 +180,7 @@ in Echo = null; # pour le système de notification du wiki Gadgets = null; # pour avoir un système d'outils activable par les users ImageMap = null; # pour mettre des widgets sur des images + Linter = null; # requis pour DiscussionTools Math = null; # pour avoir des maths LaTeX MultimediaViewer = null; # pour avoir un affichage sympa des images Nuke = null; # pour purger des pages diff --git a/secrets/mediakiwi/mediawiki-openid.age b/secrets/mediakiwi/mediawiki-openid.age new file mode 100644 index 0000000000000000000000000000000000000000..a6cf159823c26141aab0d5931596ea8c67c81696 GIT binary patch literal 1971 zcmZXTTgc>g8Hd+}ikqtSVlPBN22@r$e4ET9Gs&P`&0!{!WRgk#le1Wy$>f~LJzGB|O!!_jCk>D4x&`%UjKw~UeEZi+QW@?_Ml{AACM z4;#Vc7V}X_^pnmvhF}}+M5wbfd{7C}3LZ5_S;&XE5)VB>o`&ey<@h}?Rw$EaHRq83 zre$G$k_|`Wp4-q4+Vsd0M`I9ZxuXLt8|UI+9pVlqZ}<+B{fcU)HWk|OB!-tfMR@}NW3j*VcwbDm;$mM5sVRI4dc{|1JT$-akwr)L{E-1ey zs&Z4)Chi-U!)gU#7Ht}J0XKxGqqdXLiSIDk)R`4a(ui?<%_Q8b$5i<@w?33P0OR@Q-ZgI`ZkJiyJ57`jsQEtj=yG@OqLgg zV2<)xwT>o(l;+HMifm8s7pWF86(dm@jw|X=#JK=PJ2&=OFkYF7wRdd`Z?!I|(srAa zWor!d9Wzz&vy|Ly9Z4Ig!t1q}im_wKw$8lN+T0Yu0>#|AutHXw8uri3O zDN~XXUNiD$aF~%|ewcY1U20mfn-JzQ=C%vAfHu>0HqDQRMdj(Rfw=TR*{i?^yPcGv z3w+Hunz9U&X%cJ#9Kb_jyH1N8t(nt<6Q`H}St{>uCsVa8d3i=qIG>~wMcjI4H+d1mwt)Gv$!uzsCHbUHN6`F z($(hvQxW*^&~$pbIR@x3QPOkA?pkX9_n z9?$E05p#C7?P#``_6Pc6fvcw_-IRc4Mmd(JB18}KkrsRCVX1E>2I#tYMb5e+T6;90 z{56qh1Ox9vNkOtS`{)Zd9yt5mw_G{re&ppxze>(ubM~&APn~@G z?{_?X-+R1Q9=tky1b^kpnz-x6^2L9guYUO1J5H{>|HAWcH}cnh_~VcNZjMt|MS;pWPn^n(|!-uC-*KmF^)Up@Ebku5(jUebPi-@JE;y!XjpJof|W z`Uh`&=?l;P=-%$)OAk!0|KYdEXBO8ktKsK9aL=!A=brlcRZv_wGq~+%-+2dAKlAXT zUwU8k#0#H#lXtx`-*xHf(TA!}4(_`0>FD9*jb|Ua|Nig8_x{B@d-nA=#gEOEJI_6F qrTJQty|u^x&3*A(*c)z;&ph_U&(9zE=e50b`J2Cc`LVCBKJhOCg{j2= literal 0 HcmV?d00001 From 89c1390144fa3e3de44437ecd7f38d07bf0213b0 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Tue, 24 Feb 2026 20:11:32 +0100 Subject: [PATCH 34/77] =?UTF-8?q?fix:=20openid=20deuxi=C3=A8me=20essai?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hosts/vm/mediakiwi/mediawiki.nix | 10 ++-------- secrets/mediakiwi/mediawiki-openid.age | Bin 1971 -> 2001 bytes 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 7a0293d..2f706bc 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -106,8 +106,6 @@ in $wgPluggableAuth_EnableLocalLogin = true; $LDAPAuthentication2AllowLocalLogin = true; $LDAPProviderDomainConfigs = "${config.age.secrets.mediawiki-ldap.path}"; - # On importe les secrets pour l'OpenID. - require '${config.age.secrets.mediawiki-openid.path}'; $wgPluggableAuth_Config = [ "Compte Crans" => [ 'plugin' => 'LDAPAuthentication2', @@ -117,14 +115,10 @@ in ], "Note BDE" => [ 'plugin' => 'OpenIDConnect', - 'data' => [ - 'providerURL' => 'https://note.crans.org/o/', - 'clientID' => $SecretAgeOpenIDClientID, - 'clientsecret' => $SecretAgeOpenIDClientSecret, - ] + 'data' => require('${config.age.secrets.mediawiki-openid.path}'), ] ]; - + # Theme $wgDefaultSkin = 'citizen'; $wgCitizenThemeColor = '#AD1F1F'; diff --git a/secrets/mediakiwi/mediawiki-openid.age b/secrets/mediakiwi/mediawiki-openid.age index a6cf159823c26141aab0d5931596ea8c67c81696..1f007a551bff217927f2a5592db7104f7d3436b6 100644 GIT binary patch literal 2001 zcmZY9Z|K|x83*t&hKvk{9h;({o*)WlKXaGMziV}pf0zH4OLECwG8`niTrQWpOD>no zzrv~vtg{Yus5l2&DYF!6nNr*zx4QX43gWcF+#8*`tqvz@$5wEbsy|=!g+JduZ$8iS z<#`@A4EBPxpVes|CF@TnSqK#!VDFyRc01n-rZ9?jdac4Ctgvi#hMhq0pibN=wpQEg z$-6|eLRp0?j1EaGUSXRSb2lqxbWIJx$TV*nB)hjgC`^#!v(}A zAOIl=VQn$c)$ItJc2@*VmJQ`9akKKQd8x|qnocc{VAxhn8yqu3lb)**&GoBepFbmYo-FQfd07Apm zOl@#gZ3>GHG|wY!jdW_$iJYi|R#P<{R?*T@$9j1~%eHPyXI+OP0on@1O=UY2!t%S> zE?tP*j*Q4dMPBj*Z3>;~y5E!xg$#8H<8WJJHY0)cgE;aOD$sI(qVu^GklGB@*Sgzn z`)ylY%*g>J7AOfT_dAZ%kpVKe2Jw3F^wRh34e zZB>(0C?UqgNWRV!LQmqAM~rz-uH~d8@r~~p2w33Q8d?KrJYvvo$%J(Zbnh{Ua)HzlalfpT4|Ae8PyUVlTXOJHUjh+Zim!wZ(o_K3dW3!EJd z_$s&AjyPU26bi$(%8TWa-wn)wT}=B7HC|UE&q-Gy;8s|FnzGicU+ffpqV>T9GpH4% zdSj^{d9j#>V{FtXn-GI%qfwPYjYj28$3*ZpyfF$u5fx3B$2Djci8uDtq}$yQQ7Oze zmK2g4Hsqy@T9Z^?vnk45xJtE6IlVL8N%@%d77lP@HEx`<2t|4jwsgzDsm(H0bp{;c zaBn%3xGu(KYYJBBz@Ci8#tN?%7Hg3}x)EWLcF3&2c+P0rs0$;@p_ zevtc+K~Zy-&_jk6s{$dsD9=hFvFZQQJe{{JvQ>rz;n1RqBg#lM<1!fLETj^+6?rwh z+$LoS8{JH(5K;h`K(M7)Q^7_jhfLTkvD6NIK5)043@t@I#)$^s7-Vl^ADoR3$Gw1T zjhvYY(n6qBB0icAR)r@Oqe&)t4)EvOwpbdcbpRMxSPtTo1k_|tF$7kTXxoBiKI)& zYx}^VpTF_&3Cg_&fBvEGT)O_?&9B_|(mCL@)tBCW_SlK<{rl9ZuhkzP{QV1Gef#?l ze(><8$>+|!a8^5Wk1+KfIcq*HJbM57(yP7CP{E52o%`2sz5={;;e_@$`d#_0C!cuv z{7-{xe|YBkqIKuVqd!}me&HSb&G){`eC)}a?|SNwpZgW}^~3&c^FM#%cX#|VJbUz! zy&stGJp9Y_v8zu^K4icC&5LJFzUO1Y-8cQ|{zE5@T=`-JO)lPdE5M!k=tmC!_$BdZ zefjd=PCtF**T;@O-Old3?Zrz!fv;S-dg1C1-~7Pjsk{I2>P7bY`#<}w@Gr()zy19$ zZu-&T_H}o8{*7CYoIG~!wMsg{KCpcL^)HjB{SP-UzjFJ%-+pdg8Hd+}ikqtSVlPBN22@r$e4ET9Gs&P`&0!{!WRgk#le1Wy$>f~LJzGB|O!!_jCk>D4x&`%UjKw~UeEZi+QW@?_Ml{AACM z4;#Vc7V}X_^pnmvhF}}+M5wbfd{7C}3LZ5_S;&XE5)VB>o`&ey<@h}?Rw$EaHRq83 zre$G$k_|`Wp4-q4+Vsd0M`I9ZxuXLt8|UI+9pVlqZ}<+B{fcU)HWk|OB!-tfMR@}NW3j*VcwbDm;$mM5sVRI4dc{|1JT$-akwr)L{E-1ey zs&Z4)Chi-U!)gU#7Ht}J0XKxGqqdXLiSIDk)R`4a(ui?<%_Q8b$5i<@w?33P0OR@Q-ZgI`ZkJiyJ57`jsQEtj=yG@OqLgg zV2<)xwT>o(l;+HMifm8s7pWF86(dm@jw|X=#JK=PJ2&=OFkYF7wRdd`Z?!I|(srAa zWor!d9Wzz&vy|Ly9Z4Ig!t1q}im_wKw$8lN+T0Yu0>#|AutHXw8uri3O zDN~XXUNiD$aF~%|ewcY1U20mfn-JzQ=C%vAfHu>0HqDQRMdj(Rfw=TR*{i?^yPcGv z3w+Hunz9U&X%cJ#9Kb_jyH1N8t(nt<6Q`H}St{>uCsVa8d3i=qIG>~wMcjI4H+d1mwt)Gv$!uzsCHbUHN6`F z($(hvQxW*^&~$pbIR@x3QPOkA?pkX9_n z9?$E05p#C7?P#``_6Pc6fvcw_-IRc4Mmd(JB18}KkrsRCVX1E>2I#tYMb5e+T6;90 z{56qh1Ox9vNkOtS`{)Zd9yt5mw_G{re&ppxze>(ubM~&APn~@G z?{_?X-+R1Q9=tky1b^kpnz-x6^2L9guYUO1J5H{>|HAWcH}cnh_~VcNZjMt|MS;pWPn^n(|!-uC-*KmF^)Up@Ebku5(jUebPi-@JE;y!XjpJof|W z`Uh`&=?l;P=-%$)OAk!0|KYdEXBO8ktKsK9aL=!A=brlcRZv_wGq~+%-+2dAKlAXT zUwU8k#0#H#lXtx`-*xHf(TA!}4(_`0>FD9*jb|Ua|Nig8_x{B@d-nA=#gEOEJI_6F qrTJQty|u^x&3*A(*c)z;&ph_U&(9zE=e50b`J2Cc`LVCBKJhOCg{j2= From 88168a9a3a6c37e0b00253c9bf1cf856868fe021 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Tue, 24 Feb 2026 20:16:18 +0100 Subject: [PATCH 35/77] fix: avec un vrai fichier php --- secrets/mediakiwi/mediawiki-openid.age | Bin 2001 -> 2028 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/secrets/mediakiwi/mediawiki-openid.age b/secrets/mediakiwi/mediawiki-openid.age index 1f007a551bff217927f2a5592db7104f7d3436b6..ceae6cf9a712d69cdae7d4c2f375c98b38573c92 100644 GIT binary patch literal 2028 zcmZA0ZRp$v9S87D-H=FM_y9h%!mX|7IRDLEF3BZs6mv-~xw|~)@^FXJCU<$dB$wRf z=`Nk18+I?Y*@ClHoZy5pw&CWBZEna=Hz^FZbyY0gic@E)RTM?-rU=&hqA&cv{k{2o z`Tf2>%X4;|LbZM;!VG2N>gq0Bn966j2!Dv*(> zAVCiJpk7j4JQ&6UcrrDFtU}g;nE{Dfvb{7#1&57VL9-BevjlZh>MHG86=j z>TDW$ySM0wgr<6pzy@VB*g#o9I5uOW3u|I(PD}PEPzd#MYt6HAyHUY9tbj?C3zDp{ zaL%xEImd#CUplSUtad8I)2U&w96=bQ=lCGJp{+Bkq0rW}M*tHgPqk_-##4slI;3I_ zLCLmti8TAJ96)h=p8`9A@?b%VHcEFDp1?4Rbi3xYR%< zJ572R^~R_)4Kgi_P|3%8u!sx|hw&Ipo);@*ofsjKnl^wh%mvx03vsd0TXfq_n{wZB zRRP~xtG+Y0qXe?Cc)`yk5svh3Im9Y+Jg*dGgj(~09|J_Vc<$vU>Efuv6-*Ms_MkVm8w=L|Z}0YTDf2PT%pnyA5KEJ1#@rHHmU z)^dDUNokKieiN+nbiFqT1wB=Ecmkc>?>)xod z5QA*xU|6y#-N<2;bsG!XD8y$9ncMA90T%>;a9aTJjJlLfS>qLDWmtt>fF%u*q)3|v z7i*oFvZf5}>Z~@b+iQiim|CO$+C^nX5*2$$Xilmo%ViQwOuO2x zA-aq&Y)I`Y9*R)vV||6v>W$M=TczVnHF z$K>ti%&RXQd+Qfxp`Q%i`}td*_~xZQF2+`P=Dv4?=*rQf`%b;|o0E^4uN?YHcINAQ z_nq4_ATID1FYI~tz?LZ8z@ I5C2vCFT;e)fB*mh literal 2001 zcmZY9Z|K|x83*t&hKvk{9h;({o*)WlKXaGMziV}pf0zH4OLECwG8`niTrQWpOD>no zzrv~vtg{Yus5l2&DYF!6nNr*zx4QX43gWcF+#8*`tqvz@$5wEbsy|=!g+JduZ$8iS z<#`@A4EBPxpVes|CF@TnSqK#!VDFyRc01n-rZ9?jdac4Ctgvi#hMhq0pibN=wpQEg z$-6|eLRp0?j1EaGUSXRSb2lqxbWIJx$TV*nB)hjgC`^#!v(}A zAOIl=VQn$c)$ItJc2@*VmJQ`9akKKQd8x|qnocc{VAxhn8yqu3lb)**&GoBepFbmYo-FQfd07Apm zOl@#gZ3>GHG|wY!jdW_$iJYi|R#P<{R?*T@$9j1~%eHPyXI+OP0on@1O=UY2!t%S> zE?tP*j*Q4dMPBj*Z3>;~y5E!xg$#8H<8WJJHY0)cgE;aOD$sI(qVu^GklGB@*Sgzn z`)ylY%*g>J7AOfT_dAZ%kpVKe2Jw3F^wRh34e zZB>(0C?UqgNWRV!LQmqAM~rz-uH~d8@r~~p2w33Q8d?KrJYvvo$%J(Zbnh{Ua)HzlalfpT4|Ae8PyUVlTXOJHUjh+Zim!wZ(o_K3dW3!EJd z_$s&AjyPU26bi$(%8TWa-wn)wT}=B7HC|UE&q-Gy;8s|FnzGicU+ffpqV>T9GpH4% zdSj^{d9j#>V{FtXn-GI%qfwPYjYj28$3*ZpyfF$u5fx3B$2Djci8uDtq}$yQQ7Oze zmK2g4Hsqy@T9Z^?vnk45xJtE6IlVL8N%@%d77lP@HEx`<2t|4jwsgzDsm(H0bp{;c zaBn%3xGu(KYYJBBz@Ci8#tN?%7Hg3}x)EWLcF3&2c+P0rs0$;@p_ zevtc+K~Zy-&_jk6s{$dsD9=hFvFZQQJe{{JvQ>rz;n1RqBg#lM<1!fLETj^+6?rwh z+$LoS8{JH(5K;h`K(M7)Q^7_jhfLTkvD6NIK5)043@t@I#)$^s7-Vl^ADoR3$Gw1T zjhvYY(n6qBB0icAR)r@Oqe&)t4)EvOwpbdcbpRMxSPtTo1k_|tF$7kTXxoBiKI)& zYx}^VpTF_&3Cg_&fBvEGT)O_?&9B_|(mCL@)tBCW_SlK<{rl9ZuhkzP{QV1Gef#?l ze(><8$>+|!a8^5Wk1+KfIcq*HJbM57(yP7CP{E52o%`2sz5={;;e_@$`d#_0C!cuv z{7-{xe|YBkqIKuVqd!}me&HSb&G){`eC)}a?|SNwpZgW}^~3&c^FM#%cX#|VJbUz! zy&stGJp9Y_v8zu^K4icC&5LJFzUO1Y-8cQ|{zE5@T=`-JO)lPdE5M!k=tmC!_$BdZ zefjd=PCtF**T;@O-Old3?Zrz!fv;S-dg1C1-~7Pjsk{I2>P7bY`#<}w@Gr()zy19$ zZu-&T_H}o8{*7CYoIG~!wMsg{KCpcL^)HjB{SP-UzjFJ%-+pd Date: Wed, 18 Mar 2026 22:57:19 +0100 Subject: [PATCH 36/77] Add new namespaces --- hosts/vm/mediakiwi/mediawiki.nix | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 2f706bc..f30d609 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -143,12 +143,23 @@ in # Custom Namespaces define("NS_ARCHIVE", 3000); define("NS_ARCHIVE_TALK", 3001); - $wgExtraNamespaces[NS_ARCHIVE] = "Archive"; - $wgExtraNamespaces[NS_ARCHIVE_TALK] = "Discussion_archive"; + define("NS_PUBLIC", 3002); + define("NS_PUBLIC_TALK", 3003); + define("NS_INTERNE", 3004); + define("NS_INTERNE_TALK", 3005); + $wgExtraNamespaces[ + NS_ARCHIVE => "Archive", + NS_ARCHIVE_TALK => "Discussion_archive", + NS_PUBLIC => "Public", + NS_PUBLIC_TALK => "Discussions_pages_publiques", + NS_INTERNE => "Interne", + NS_INTERNE_TALK => "Discussions_pages_internes" + ]; # Debug $wgShowExceptionDetails = true; $wgDebugToolbar = true; + $wgDebugLogFile = "/var/log/mediawiki/debug.log"; # $wgShowDebug = true; # $wgDevelopmentWarnings = true; ''; From a0d43fc54c10a6aa6a475a01785d425729a32b51 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Wed, 18 Mar 2026 23:01:54 +0100 Subject: [PATCH 37/77] Set Timezone to Paris --- hosts/vm/mediakiwi/mediawiki.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index f30d609..0aff05a 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -126,6 +126,8 @@ in $wgCitizenEnableCJKFonts = true; $wgLanguageCode = 'fr'; + $wgLocaltimezone = 'Europe/Paris'; + $wgDefaultUserOptions['timecorrection'] = 'ZoneInfo|0|' . $wgLocaltimezone; # Disable anonymous editing $wgGroupPermissions['*']['edit'] = false; From c078699bf988f4a6000ff2b058b1e5b83a07aeaf Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Wed, 18 Mar 2026 23:10:34 +0100 Subject: [PATCH 38/77] Change content namespaces --- hosts/vm/mediakiwi/mediawiki.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 0aff05a..e0c58de 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -157,6 +157,7 @@ in NS_INTERNE => "Interne", NS_INTERNE_TALK => "Discussions_pages_internes" ]; + $wgContentNamespaces = [ NS_MAIN, NS_PUBLIC, NS_INTERNE ]; # Debug $wgShowExceptionDetails = true; From cafa89b7e7ae34b35953bb28d2d20b214c03e63e Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Wed, 18 Mar 2026 23:11:58 +0100 Subject: [PATCH 39/77] Set content namespaces --- hosts/vm/mediakiwi/mediawiki.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index e0c58de..c0217f0 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -149,7 +149,7 @@ in define("NS_PUBLIC_TALK", 3003); define("NS_INTERNE", 3004); define("NS_INTERNE_TALK", 3005); - $wgExtraNamespaces[ + $wgExtraNamespaces = [ NS_ARCHIVE => "Archive", NS_ARCHIVE_TALK => "Discussion_archive", NS_PUBLIC => "Public", From 55767e5690bd78246bf5281d1f369ff67be9085c Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Mon, 23 Mar 2026 22:20:33 +0100 Subject: [PATCH 40/77] =?UTF-8?q?feat:=E2=80=AFOAuth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hosts/vm/mediakiwi/WSONoteKfetAuth/README | 14 ++ .../mediakiwi/WSONoteKfetAuth/extension.json | 25 ++++ .../WSONoteKfetAuth/src/NoteKfetAuth.php | 137 ++++++++++++++++++ hosts/vm/mediakiwi/mediawiki.nix | 16 +- secrets/mediakiwi/mediawiki-oauth.age | Bin 0 -> 2000 bytes secrets/mediakiwi/mediawiki-openid.age | Bin 2028 -> 0 bytes 6 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 hosts/vm/mediakiwi/WSONoteKfetAuth/README create mode 100644 hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json create mode 100644 hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php create mode 100644 secrets/mediakiwi/mediawiki-oauth.age delete mode 100644 secrets/mediakiwi/mediawiki-openid.age diff --git a/hosts/vm/mediakiwi/WSONoteKfetAuth/README b/hosts/vm/mediakiwi/WSONoteKfetAuth/README new file mode 100644 index 0000000..0d3eae9 --- /dev/null +++ b/hosts/vm/mediakiwi/WSONoteKfetAuth/README @@ -0,0 +1,14 @@ +# NoteKfetAuth + +Extension pour médiawiki pour ajouter le support de l'autentification par Note +via l'extension WSOAuth. + +## Installation + +Il faut enregistrer l'extension comme provider + +``` +$wgOAuthCustomAuthProviders = [ + 'note' => WSOAuthNoteKfetAuth\NoteKfetAuth::class +]; +``` diff --git a/hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json b/hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json new file mode 100644 index 0000000..efbb583 --- /dev/null +++ b/hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json @@ -0,0 +1,25 @@ +{ + "name": "WSONoteKfetAuth", + "author": [ + "Pyjacpp" + ], + "url": "https://gitlab.crans.org/nounou/WSONoteKfet", + "description": "Implementation of the NoteKfet OAuth2 for the WSOAuth extension", + "type": "other", + "requires": { + "MediaWiki": ">= 1.35.0", + "extensions": { + "WSOAuth": ">= 9.0" + } + }, + "AutoloadNamespaces": { + "WSONoteKfetAuth\\": "src/" + }, + "config": { + "NoteKfetUrl": { + "description": "The url of the NoteKfet.", + "value": "https://note.crans.org/" + } + }, + "manifest_version": 2 +} \ No newline at end of file diff --git a/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php b/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php new file mode 100644 index 0000000..464213c --- /dev/null +++ b/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php @@ -0,0 +1,137 @@ +clientId = $clientId; + $this->clientSecret = $clientSecret; + } + + /** + * @inheritDoc + */ + public function login( ?string &$key, ?string &$secret, ?string &$authUrl ): bool { + + $state = random_int(PHP_INT_MIN, PHP_INT_MAX); + $secret = "$state"; + $authUrl = $GLOBALS['wgNoteKfetUrl'] . "o/authorize/?" . http_build_query( + 'client_id' => $this->clientId, + 'response_type' => 'code', + 'scope' => '1_1', + 'state' => $secret, + ); + + return true; + } + + /** + * @inheritDoc + */ + public function logout( UserIdentity &$user ): void { + } + + /** + * @inheritDoc + */ + public function getUser( string $key, string $secret, &$errorMessage ) { + if ( !isset( $_GET['code'] ) ) { + return false; + } + + if ( !isset( $_GET['state'] ) || empty( $_GET['state'] ) || ( $_GET['state'] !== $secret ) ) { + return false; + } + + try { + $token = $this->getAccessTokens( $_GET['code'] ); + $userInfos = $this->getUserInfos( $token ); + + return [ + 'name' => $userInfos->normalized_name, + 'realname' => $userInfos->username, + 'email' => $userInfos->email, + ]; + } catch ( \Exception $e ) { + return false; + } + } + + private function getAccessTokens( string $code ) { + $data = [ + 'grant_type' => 'authorization_code', + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + 'code' => $code, + ]; + + $options = [ + 'http' => [ + 'method' => 'POST', + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'content' => http_build_query($data), + ], + ]; + + $context = stream_context_create($options); + $response = file_get_contents($GLOBALS['wgNoteKfetUrl'] . 'o/oauth/token/', false, $context); + $tokens = json_decode($response); + return $tokens->access_token; + } + + private function getUserInfos( string $token ) { + $options = [ + 'http' => [ + 'method' => 'GET', + 'header' => "Authorization: Bearer $token", + ], + ]; + + $context = stream_context_create($options); + $response = file_get_contents($GLOBALS['wgNoteKfetUrl'] . 'api/me/', false, $context); + return json_decode($response); + } + + /** + * @inheritDoc + */ + public function saveExtraAttributes( int $id ): void { + } +} diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index c0217f0..15cd91b 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -106,6 +106,9 @@ in $wgPluggableAuth_EnableLocalLogin = true; $LDAPAuthentication2AllowLocalLogin = true; $LDAPProviderDomainConfigs = "${config.age.secrets.mediawiki-ldap.path}"; + $wgOAuthCustomAuthProviders = [ + 'note' => WSOAuthNoteKfetAuth\NoteKfetAuth::class + ]; $wgPluggableAuth_Config = [ "Compte Crans" => [ 'plugin' => 'LDAPAuthentication2', @@ -114,8 +117,8 @@ in ] ], "Note BDE" => [ - 'plugin' => 'OpenIDConnect', - 'data' => require('${config.age.secrets.mediawiki-openid.path}'), + 'plugin' => 'WSOAuth', + 'data' => require('${config.age.secrets.mediawiki-oauth.path}'), ] ]; @@ -257,15 +260,16 @@ in # Pas de meilleure solution à ma connaissance pour suivre les releases. sha256 = "sha256-oi5rliHb4KnLbvQxO7MGuLp/FEucoGR/Z0NP1gmbgMc="; }; - OpenIDConnect = pkgs.fetchFromGitHub { - name = "OpenIDConnect"; + WSOAuth = pkgs.fetchFromGitHub { + name = "WSOAuth"; owner = "wikimedia"; - repo = "mediawiki-extensions-OpenIDConnect"; + repo = "mediawiki-extensions-WSOAuth"; rev = "REL" + major + "_" + minor; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-KoBULn53xnY+ydodeTGN7YEoqgLr9qhhuR5mNibbh5s="; + sha256 = "sha256-1jv4nmkxky1dx7833sacsbqww54yb9glrnl1360b13h2z5fhwdxp"; }; + WSONoteKfetAuth = ./WSONoteKfetAuth; }; }; } diff --git a/secrets/mediakiwi/mediawiki-oauth.age b/secrets/mediakiwi/mediawiki-oauth.age new file mode 100644 index 0000000000000000000000000000000000000000..e442571d799062c48266bba318cd37f6914617ec GIT binary patch literal 2000 zcmZXU>&xT@8O2vc#e5Q>t6LGqij+3{nn@AVn~|XyFtOiHBM{iFbt9JK%OTT zU@g0x5GPIsaTdY0gb&w!Y3?a4P>58Ls&Qmd8ss>WE(9)gsYuMC?SmA%H*_V+hEn6j zqy15e-kj7Fze72`(b5~ULD-lKX9c~?hK=H(4Jl`V7%SAcxz6 z_zrWjZr=uTp)wQ3NCyin@OVh&mhl9s)@URt3dQMeLAxQa!{}wr*J3sc5VcU-vC zr6Th^*=?-UhPxY~X!R$uwoFAnrFC7HR$Qf&@fwwME*oDou+C&a6nOsscwP z=)&sNsb=qOA~gd?!gonV>nm8SR2i7&DaA$J}I) zgQaQ(f+nRS#>5k`Z8^^Ll@X(}Rzqbt!A&I^l7Vcmx@BGyEY8dkT$8qtUrnZUtQtK? zFKpgai8Wg7xOA>*wb_hkZj?s2I~~nHrdU^9K*M{wY5AT>+a;j|V3^nvx`Et?PU~o~ zt}_IwbHok#LFuj5EV)InJ~1DymR-SVLxcxFa=IfYBC837F7MWcja5yQYc$5s`f(X( zOkl<&%vh_ctxFd}^Twz5jM>NWzFe{c5jgsYQz(=@zq5)NzQ*i%%Xb-m@pvS#|5T~IC^j+SOzm!u##>gx5x?`uxGfxAc5 zm|37O`wlEGF})p?lL_C_1lk+Vl@PR}CDE2B-c%?Q3OET4bWHI9XO&0^Q91cM0#h4< z90N*lg{Oo?FC3*5$Bh;o7|o1z_G+R3d#b#JK$c`@fNr?~)ko%Wo#?(2uC{RmtID4B z_+>y>iYF7!e&ek-6BCef#4Fh-n~pTnaU?s^l5)e%N611E2Ky=mJvYpMl1kw#7b-2(+)F3>pbu?M&h@{)-MyR$tB#+&r*(Sa*@W>8_ z2NbU!PUwoQL!R%orMAhthl2_n92|5sQ8SVSxpdIXz^C@oY(TWxWl~PsYfN)$%i>B>fdhEZ z1x}s$MCXsqg`d9nvL&l`UBB+U@u5q_=N>)v*|)#*Df#cRcdMlaFB1Q}6G+e9^{jb? zg#Z2e^~au-;`{!9{Qh3OJU@T%lMmeS81gcF{Mg0&&+e+fep{K`{Jk3S@y}7AG-U( zS1+CU&<)JR;NtH-IN$!N^U@1%J$>y>Kfd~n^3t8ur{a%(=JXTJ1IJ#x|D_90U%Txl z?RfaSc%X4;|LbZM;!VG2N>gq0Bn966j2!Dv*(> zAVCiJpk7j4JQ&6UcrrDFtU}g;nE{Dfvb{7#1&57VL9-BevjlZh>MHG86=j z>TDW$ySM0wgr<6pzy@VB*g#o9I5uOW3u|I(PD}PEPzd#MYt6HAyHUY9tbj?C3zDp{ zaL%xEImd#CUplSUtad8I)2U&w96=bQ=lCGJp{+Bkq0rW}M*tHgPqk_-##4slI;3I_ zLCLmti8TAJ96)h=p8`9A@?b%VHcEFDp1?4Rbi3xYR%< zJ572R^~R_)4Kgi_P|3%8u!sx|hw&Ipo);@*ofsjKnl^wh%mvx03vsd0TXfq_n{wZB zRRP~xtG+Y0qXe?Cc)`yk5svh3Im9Y+Jg*dGgj(~09|J_Vc<$vU>Efuv6-*Ms_MkVm8w=L|Z}0YTDf2PT%pnyA5KEJ1#@rHHmU z)^dDUNokKieiN+nbiFqT1wB=Ecmkc>?>)xod z5QA*xU|6y#-N<2;bsG!XD8y$9ncMA90T%>;a9aTJjJlLfS>qLDWmtt>fF%u*q)3|v z7i*oFvZf5}>Z~@b+iQiim|CO$+C^nX5*2$$Xilmo%ViQwOuO2x zA-aq&Y)I`Y9*R)vV||6v>W$M=TczVnHF z$K>ti%&RXQd+Qfxp`Q%i`}td*_~xZQF2+`P=Dv4?=*rQf`%b;|o0E^4uN?YHcINAQ z_nq4_ATID1FYI~tz?LZ8z@ I5C2vCFT;e)fB*mh From baf742483d36f88113e6a863b7071fe0750cb4dc Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Mon, 23 Mar 2026 23:48:34 +0100 Subject: [PATCH 41/77] feat: OAuth Note repairs --- hosts/vm/mediakiwi/WSONoteKfetAuth/README | 2 +- hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json | 2 +- .../vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php | 8 ++++---- hosts/vm/mediakiwi/mediawiki.nix | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/hosts/vm/mediakiwi/WSONoteKfetAuth/README b/hosts/vm/mediakiwi/WSONoteKfetAuth/README index 0d3eae9..b13c920 100644 --- a/hosts/vm/mediakiwi/WSONoteKfetAuth/README +++ b/hosts/vm/mediakiwi/WSONoteKfetAuth/README @@ -9,6 +9,6 @@ Il faut enregistrer l'extension comme provider ``` $wgOAuthCustomAuthProviders = [ - 'note' => WSOAuthNoteKfetAuth\NoteKfetAuth::class + 'note' => WSOAuth\AuthenticationProvider\NoteKfetAuth::class ]; ``` diff --git a/hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json b/hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json index efbb583..b4d160f 100644 --- a/hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json +++ b/hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json @@ -13,7 +13,7 @@ } }, "AutoloadNamespaces": { - "WSONoteKfetAuth\\": "src/" + "WSOAuth\\AuthenticationProvider\\": "src/" }, "config": { "NoteKfetUrl": { diff --git a/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php b/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php index 464213c..73b5305 100644 --- a/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php +++ b/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php @@ -52,12 +52,12 @@ class NoteKfetAuth extends AuthProvider { $state = random_int(PHP_INT_MIN, PHP_INT_MAX); $secret = "$state"; - $authUrl = $GLOBALS['wgNoteKfetUrl'] . "o/authorize/?" . http_build_query( + $authUrl = $GLOBALS['wgNoteKfetUrl'] . "o/authorize/?" . http_build_query([ 'client_id' => $this->clientId, 'response_type' => 'code', 'scope' => '1_1', 'state' => $secret, - ); + ]); return true; } @@ -85,7 +85,7 @@ class NoteKfetAuth extends AuthProvider { $userInfos = $this->getUserInfos( $token ); return [ - 'name' => $userInfos->normalized_name, + 'name' => "note_$userInfos->normalized_name", 'realname' => $userInfos->username, 'email' => $userInfos->email, ]; @@ -111,7 +111,7 @@ class NoteKfetAuth extends AuthProvider { ]; $context = stream_context_create($options); - $response = file_get_contents($GLOBALS['wgNoteKfetUrl'] . 'o/oauth/token/', false, $context); + $response = file_get_contents($GLOBALS['wgNoteKfetUrl'] . 'o/token/', false, $context); $tokens = json_decode($response); return $tokens->access_token; } diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 15cd91b..f3c2ec7 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -16,8 +16,8 @@ in owner = "mediawiki"; }; - age.secrets.mediawiki-openid = { - file = ../../../secrets/mediakiwi/mediawiki-openid.age; + age.secrets.mediawiki-oauth = { + file = ../../../secrets/mediakiwi/mediawiki-oauth.age; owner = "mediawiki"; }; @@ -107,7 +107,7 @@ in $LDAPAuthentication2AllowLocalLogin = true; $LDAPProviderDomainConfigs = "${config.age.secrets.mediawiki-ldap.path}"; $wgOAuthCustomAuthProviders = [ - 'note' => WSOAuthNoteKfetAuth\NoteKfetAuth::class + 'note' => WSOAuth\AuthenticationProvider\NoteKfetAuth::class ]; $wgPluggableAuth_Config = [ "Compte Crans" => [ @@ -267,9 +267,9 @@ in rev = "REL" + major + "_" + minor; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-1jv4nmkxky1dx7833sacsbqww54yb9glrnl1360b13h2z5fhwdxp"; + sha256 = "sha256-G2C/KuSgfWWftpHHhOABwFcOEb1zB8qmHi9KgrC6Jrc="; }; - WSONoteKfetAuth = ./WSONoteKfetAuth; + WSONoteKfetAuth = "${./WSONoteKfetAuth}"; }; }; } From 6f1449a54632ce5e891cdd641ebcb78c493200a9 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Wed, 22 Apr 2026 18:43:45 +0200 Subject: [PATCH 42/77] fix: phpExtensions version --- hosts/vm/mediakiwi/mediawiki.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index f3c2ec7..4933a2c 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -35,8 +35,8 @@ in max_execution_time = 1000 max_input_time = 2000 default_socket_timeout = 2000 - extension = ${pkgs.phpExtensions.mbstring}/lib/php/extensions/mbstring.so - extension = ${pkgs.phpExtensions.luasandbox}/lib/php/extensions/luasandbox.so + extension = ${pkgs.php85Extensions.mbstring}/lib/php/extensions/mbstring.so + extension = ${pkgs.php85Extensions.luasandbox}/lib/php/extensions/luasandbox.so ''; services.nginx.clientMaxBodySize = "512M"; From 01dfd8506e1d4f48bc01743ca616b9777b1fff3b Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Thu, 23 Apr 2026 19:13:17 +0200 Subject: [PATCH 43/77] Fix phpExtensions and synchronize on mediawiki --- hosts/vm/mediakiwi/mediawiki.nix | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 4933a2c..3d2756b 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -4,6 +4,7 @@ let version = pkgs.mediawiki.version; major = lib.versions.major version; minor = lib.versions.minor version; + phpExtensions = config.services.mediawiki.phpPackage.extensions; in { age.secrets.mediawiki-admin-passwd = { @@ -35,8 +36,8 @@ in max_execution_time = 1000 max_input_time = 2000 default_socket_timeout = 2000 - extension = ${pkgs.php85Extensions.mbstring}/lib/php/extensions/mbstring.so - extension = ${pkgs.php85Extensions.luasandbox}/lib/php/extensions/luasandbox.so + extension = ${phpExtensions.mbstring}/lib/php/extensions/mbstring.so + extension = ${phpExtensions.luasandbox}/lib/php/extensions/luasandbox.so ''; services.nginx.clientMaxBodySize = "512M"; From d8f322399cc199d824712c7c06ca208cc19f83ff Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sun, 26 Apr 2026 19:33:50 +0200 Subject: [PATCH 44/77] Sanitize Note Usernames --- .../mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php b/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php index 73b5305..fd2755d 100644 --- a/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php +++ b/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php @@ -50,6 +50,8 @@ class NoteKfetAuth extends AuthProvider { */ public function login( ?string &$key, ?string &$secret, ?string &$authUrl ): bool { + // This state is used to prevent CSRF, i.e., ensuring that authentification request + // were initiated on our website. $state = random_int(PHP_INT_MIN, PHP_INT_MAX); $secret = "$state"; $authUrl = $GLOBALS['wgNoteKfetUrl'] . "o/authorize/?" . http_build_query([ @@ -85,7 +87,7 @@ class NoteKfetAuth extends AuthProvider { $userInfos = $this->getUserInfos( $token ); return [ - 'name' => "note_$userInfos->normalized_name", + 'name' => this->sanitizeName( "$userInfos->normalized_name (note)" ), 'realname' => $userInfos->username, 'email' => $userInfos->email, ]; @@ -94,6 +96,15 @@ class NoteKfetAuth extends AuthProvider { } } + private function sanitizeName( string $name ) { + // We replace forbidden chars. + $res = preg_replace('/[#\/:<>=@\|]/', '-', $name); + $res = preg_replace(['/[\[{]/', '/[\]}]/'], ['(', ')'], $res); + $res = str_replace('_', ' ', $res); + // We remove the last controls chars possibly remaining. + return preg_replace('/[^a-zA-Z0-9 !\"$%&\'()*+,\-.;?\\\^`~]/', '', $res); + } + private function getAccessTokens( string $code ) { $data = [ 'grant_type' => 'authorization_code', From c232b7b67f9766a928bb1f0b3a22aa0110be47a2 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Mon, 23 Mar 2026 23:51:50 +0100 Subject: [PATCH 45/77] fix some typos --- hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json b/hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json index b4d160f..d4c303d 100644 --- a/hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json +++ b/hosts/vm/mediakiwi/WSONoteKfetAuth/extension.json @@ -3,7 +3,7 @@ "author": [ "Pyjacpp" ], - "url": "https://gitlab.crans.org/nounou/WSONoteKfet", + "url": "https://gitlab.crans.org/nounous/nixos/-/tree/main/hosts/vm/mediakiwi/WSONoteKfetAuth", "description": "Implementation of the NoteKfet OAuth2 for the WSOAuth extension", "type": "other", "requires": { @@ -22,4 +22,4 @@ } }, "manifest_version": 2 -} \ No newline at end of file +} From bb48ba5e3406f811d9e712acbf55c552b17effca Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Fri, 1 May 2026 12:12:27 +0200 Subject: [PATCH 46/77] Add search namespaces --- hosts/vm/mediakiwi/mediawiki.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 3d2756b..8ec87d1 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -162,6 +162,11 @@ in NS_INTERNE_TALK => "Discussions_pages_internes" ]; $wgContentNamespaces = [ NS_MAIN, NS_PUBLIC, NS_INTERNE ]; + $wgNamespacesToBeSearchedDefault = [ + NS_MAIN => true, + NS_PUBLIC => true, + NS_INTERNE => true, + ]; # Debug $wgShowExceptionDetails = true; From 42e9125152d4aae5c0c670ae15d1f2f2fde6764c Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Fri, 1 May 2026 12:16:25 +0200 Subject: [PATCH 47/77] Fix syntax typo --- hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php b/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php index fd2755d..5f71d86 100644 --- a/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php +++ b/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php @@ -87,7 +87,7 @@ class NoteKfetAuth extends AuthProvider { $userInfos = $this->getUserInfos( $token ); return [ - 'name' => this->sanitizeName( "$userInfos->normalized_name (note)" ), + 'name' => $this->sanitizeName( "$userInfos->normalized_name (note)" ), 'realname' => $userInfos->username, 'email' => $userInfos->email, ]; From a4b467362d84206afc03d10c847385a710f305bf Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sun, 3 May 2026 17:13:21 +0200 Subject: [PATCH 48/77] fix: use note id instead --- hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php | 1 + hosts/vm/mediakiwi/mediawiki.nix | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php b/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php index 5f71d86..4a04205 100644 --- a/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php +++ b/hosts/vm/mediakiwi/WSONoteKfetAuth/src/NoteKfetAuth.php @@ -90,6 +90,7 @@ class NoteKfetAuth extends AuthProvider { 'name' => $this->sanitizeName( "$userInfos->normalized_name (note)" ), 'realname' => $userInfos->username, 'email' => $userInfos->email, + 'remoteUserId' => $userInfos->id, ]; } catch ( \Exception $e ) { return false; diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 8ec87d1..acf8cad 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -268,8 +268,8 @@ in }; WSOAuth = pkgs.fetchFromGitHub { name = "WSOAuth"; - owner = "wikimedia"; - repo = "mediawiki-extensions-WSOAuth"; + owner = "pyjacpp"; + repo = "WSOAuth"; rev = "REL" + major + "_" + minor; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. From 98259437f0ee17aba05daefb069660ad19dc7bd9 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Fri, 15 May 2026 22:41:29 +0200 Subject: [PATCH 49/77] Ajout category lockdown --- hosts/vm/mediakiwi/category-lockdown.patch | 90 ++++++++++++++++++++++ hosts/vm/mediakiwi/mediawiki.nix | 45 ++++++++--- 2 files changed, 126 insertions(+), 9 deletions(-) create mode 100644 hosts/vm/mediakiwi/category-lockdown.patch diff --git a/hosts/vm/mediakiwi/category-lockdown.patch b/hosts/vm/mediakiwi/category-lockdown.patch new file mode 100644 index 0000000..3a2b092 --- /dev/null +++ b/hosts/vm/mediakiwi/category-lockdown.patch @@ -0,0 +1,90 @@ +diff --git a/CategoryLockdown.php b/CategoryLockdown.php +index 1e17ec7..caa13c1 100644 +--- a/CategoryLockdown.php ++++ b/CategoryLockdown.php +@@ -16,6 +16,8 @@ class CategoryLockdown { + */ + public static function onGetUserPermissionsErrors( $title, $user, $action, &$result ) { + global $wgCategoryLockdown; ++ global $wgCategoryGroupLockdown; ++ global $wgCategoryLockdownWhitelist; + + $explicitGroups = MediaWikiServices::getInstance()->getUserGroupManager()->getUserGroups( $user ); + $implicitGroups = MediaWikiServices::getInstance()->getUserGroupManager()->getUserImplicitGroups( $user ); +@@ -26,6 +28,11 @@ class CategoryLockdown { + return; + } + ++ // Rules doesn’t apply to the whitelist ++ if ( in_array( $title, $wgCategoryLockdownWhitelist ) ) { ++ return; ++ } ++ + $categories = array_keys( $title->getParentCategories() ); + + // Apply rules to the category page itself +@@ -33,16 +40,11 @@ class CategoryLockdown { + $categories[] = $title->getFullText(); + } + ++ // Support "Category:Top_secret", "Category:Top secret", "Top_secret" and "Top secret" ++ $categories = array_map( fn($c) => str_replace( '_', ' ', substr( $c, strpos( $c, ':' ) + 1 ) ), $categories ); + $combinedGroups = []; + foreach ( $categories as $category ) { +- // Support "Category:Top_secret", "Category:Top secret", "Top_secret" and "Top secret" +- $category = substr( $category, strpos( $category, ':' ) + 1 ); +- $category = str_replace( '_', ' ', $category ); + $permissions = $wgCategoryLockdown[ $category ] ?? null; +- if ( !$permissions ) { +- $category = str_replace( ' ', '_', $category ); +- $permissions = $wgCategoryLockdown[ $category ] ?? null; +- } + if ( !$permissions ) { + continue; + } +@@ -57,15 +59,40 @@ class CategoryLockdown { + $combinedGroups[] = $allowedGroup; + } + } +- if ( $combinedGroups ) { +- foreach ( $userGroups as $userGroup ) { +- if ( in_array( $userGroup, $combinedGroups ) ) { +- return; +- } ++ ++ $allow = false; ++ foreach ( $userGroups as $userGroup ) { ++ if ( in_array( $userGroup, $combinedGroups ) ) { ++ $allow = true; ++ break; + } ++ } ++ if ( $combinedGroups && !$allow ) { + $result = [ 'categorylockdown-error', implode( ', ', $combinedGroups ) ]; + return false; + } ++ ++ $allow = true; ++ foreach ( $wgCategoryGroupLockdown as $group => $groupCategories ) { ++ if ( str_starts_with( $group, "!") ? ++ in_array( substr($group, 1), $userGroups ) : ++ !in_array( $group, $userGroups ) ) { ++ continue; # Skip if this group rule doesn’t match the user ++ } ++ ++ $requiredCat = $groupCategories[$action] ?? []; ++ $groupLocked = true; ++ foreach ( $requiredCat as $c ) { ++ if ( in_array( $c, $categories ) ) { ++ $groupLocked = false; ++ break; # One of the category is present, we can grant this action. ++ } ++ } ++ if ( $groupLocked ) { ++ $result = [ 'categorylockdown-error', implode( ', ', $requiredCat ) ]; ++ return false; # This group of users need a category from groupCategories on this page to perform $action ++ } ++ } + } + + /** diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index acf8cad..1c6b622 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -133,11 +133,20 @@ in $wgLocaltimezone = 'Europe/Paris'; $wgDefaultUserOptions['timecorrection'] = 'ZoneInfo|0|' . $wgLocaltimezone; - # Disable anonymous editing - $wgGroupPermissions['*']['edit'] = false; - $wgGroupPermissions['*']['read'] = false; + # Access Control + $wgGroupPermissions['*']['edit'] = false; # Restrict edition for anonymous user + $wgGroupPermissions['*']['createaccount'] = false; # Restrict the creation of account to sysop only - # Extensions + $wgCategoryLockdownWhitelist = [ + "Spécial:Connexion", + "Spécial:Connexion/return", + "Spécial:PluggableAuthLogin", + "Spécial:Recherche", + "MediaWiki:Common.css", + "MediaWiki:Common.js" + ]; + + $wgCategoryGroupLockdown["!user"]["read"] = [ "Page Publique" ]; # Restrict read for non-user (i.e. anonymous) on execpt for Page Publique # Extensions $wgWikiEditorRealtimePreview = true; $wgCiteBookReferencing = true; $wgPdfProcessor = '${ pkgs.ghostscript }/bin/gs'; @@ -216,7 +225,24 @@ in VisualEditor = null; # pour éditer visuellement les pages WikiEditor = null; # pour éditer le code wiki des pages - # Bundled with next version, put as null in the next update + CategoryLockdown = pkgs.applyPatches { + src = pkgs.fetchFromGitHub { + name = "CategoryLockdown"; + owner = "wikimedia"; + repo = "mediawiki-extensions-CategoryLockdown"; + rev = "REL" + major + "_" + minor; + # Le SHA doit être changé à chaque nouveau commit de traduction. + # Pas de meilleure solution à ma connaissance pour suivre les releases. + sha256 = "sha256-WEg0QP4QXt89uvZCZvkfpycoZGBP640aTpaxbl5jsZs="; + }; + patches = [ + # Cette extension s’occupe des du contrôle d’accès du Wiki + # et a été beaucoup patché. Pensez à vérifier les changements + # et révisez le patch le cas échéant. + "${./category-lockdown.patch}" + ]; + }; + TemplateStyles = pkgs.fetchFromGitHub { name = "TemplateStyles"; owner = "wikimedia"; @@ -266,14 +292,15 @@ in # Pas de meilleure solution à ma connaissance pour suivre les releases. sha256 = "sha256-oi5rliHb4KnLbvQxO7MGuLp/FEucoGR/Z0NP1gmbgMc="; }; - WSOAuth = pkgs.fetchFromGitHub { + WSOAuth = pkgs.fetctFromGitlab { + domain = "gitlab.crans.org"; name = "WSOAuth"; owner = "pyjacpp"; - repo = "WSOAuth"; - rev = "REL" + major + "_" + minor; + repo = "wsoauth"; + rev = "master"; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-G2C/KuSgfWWftpHHhOABwFcOEb1zB8qmHi9KgrC6Jrc="; + sha256 = "sha256-8jrRuHcJZc+YNnfyYkNLwF4PEIiHfd27YnTVAZcxgoc="; }; WSONoteKfetAuth = "${./WSONoteKfetAuth}"; }; From 18c59cdc8577e45505df348dc1f640eee74f7ef0 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Fri, 15 May 2026 22:49:24 +0200 Subject: [PATCH 50/77] Add WSOAuth patch --- hosts/vm/mediakiwi/WSOAuth.patch | 38 ++++++++++++++++++++++++++++++++ hosts/vm/mediakiwi/mediawiki.nix | 20 +++++++++-------- 2 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 hosts/vm/mediakiwi/WSOAuth.patch diff --git a/hosts/vm/mediakiwi/WSOAuth.patch b/hosts/vm/mediakiwi/WSOAuth.patch new file mode 100644 index 0000000..5da4498 --- /dev/null +++ b/hosts/vm/mediakiwi/WSOAuth.patch @@ -0,0 +1,38 @@ +diff --git a/src/WSOAuth.php b/src/WSOAuth.php +index 3a94c87..e077b9e 100644 +--- a/src/WSOAuth.php ++++ b/src/WSOAuth.php +@@ -308,11 +308,12 @@ class WSOAuth extends PluggableAuth { + // Set $realname and $email to the values returned from the authentication provider, if they are available + $realname = $remoteUserInfo['realname'] ?? null; + $email = $remoteUserInfo['email'] ?? null; ++ $username = ucfirst( $remoteUserInfo['name'] ); + +- $remoteUsername = ucfirst( $remoteUserInfo['name'] ); +- $localUserId = $this->getLocalAccountID( $remoteUsername ); ++ $remoteUserId = $remoteUserInfo['remoteUserId'] ?? $username; ++ $localUserId = $this->getLocalAccountID( $remoteUserId ); + +- $this->session->set( self::WSOAUTH_REMOTE_USERNAME_SESSION_KEY, $remoteUsername ); ++ $this->session->set( self::WSOAUTH_REMOTE_USERNAME_SESSION_KEY, $remoteUserId ); + $this->session->save(); + + if ( $localUserId !== 0 ) { +@@ -326,7 +327,7 @@ class WSOAuth extends PluggableAuth { + $currentUser = RequestContext::getMain()->getUser(); + $currentUserId = $currentUser->getId(); + +- $this->createMapping( $currentUserId, $remoteUsername ); ++ $this->createMapping( $currentUserId, $remoteUserId ); + + // Log the account in like normal + $username = $currentUser->getName(); +@@ -339,7 +340,7 @@ class WSOAuth extends PluggableAuth { + throw new ContinuationException( wfMessage( "wsoauth-remote-only-accounts-disabled" )->parse() ); + } + +- $desiredLocalUsername = $this->useRealNameAsUsername && $realname !== null ? $realname : $remoteUsername; ++ $desiredLocalUsername = $this->useRealNameAsUsername && $realname !== null ? $realname : $username; + $userId = User::newFromName( $desiredLocalUsername )->idForName(); + + if ( $userId > 0 && $this->migrateUsersByUsername ) { diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 1c6b622..f908495 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -292,15 +292,17 @@ in # Pas de meilleure solution à ma connaissance pour suivre les releases. sha256 = "sha256-oi5rliHb4KnLbvQxO7MGuLp/FEucoGR/Z0NP1gmbgMc="; }; - WSOAuth = pkgs.fetctFromGitlab { - domain = "gitlab.crans.org"; - name = "WSOAuth"; - owner = "pyjacpp"; - repo = "wsoauth"; - rev = "master"; - # Le SHA doit être changé à chaque nouveau commit de traduction. - # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-8jrRuHcJZc+YNnfyYkNLwF4PEIiHfd27YnTVAZcxgoc="; + WSOAuth = pkgs.applyPatches { + src = pkgs.fetchFromGitHub { + name = "WSOAuth"; + owner = "wikimedia"; + repo = "mediawiki-extensions-WSOAuth"; + rev = "REL" + major + "_" + minor; + # Le SHA doit être changé à chaque nouveau commit de traduction. + # Pas de meilleure solution à ma connaissance pour suivre les releases. + sha256 = "sha256-G2C/KuSgfWWftpHHhOABwFcOEb1zB8qmHi9KgrC6Jrc="; + }; + patches = [ "${./WSOAuth.patch}" ]; }; WSONoteKfetAuth = "${./WSONoteKfetAuth}"; }; From eb81239e4f1f068a955e773011dd3e9c69e9a9ff Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Fri, 15 May 2026 22:50:25 +0200 Subject: [PATCH 51/77] Remove custom Public and Interne namespaces --- hosts/vm/mediakiwi/mediawiki.nix | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index f908495..98dbdb1 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -158,23 +158,9 @@ in # Custom Namespaces define("NS_ARCHIVE", 3000); define("NS_ARCHIVE_TALK", 3001); - define("NS_PUBLIC", 3002); - define("NS_PUBLIC_TALK", 3003); - define("NS_INTERNE", 3004); - define("NS_INTERNE_TALK", 3005); $wgExtraNamespaces = [ NS_ARCHIVE => "Archive", NS_ARCHIVE_TALK => "Discussion_archive", - NS_PUBLIC => "Public", - NS_PUBLIC_TALK => "Discussions_pages_publiques", - NS_INTERNE => "Interne", - NS_INTERNE_TALK => "Discussions_pages_internes" - ]; - $wgContentNamespaces = [ NS_MAIN, NS_PUBLIC, NS_INTERNE ]; - $wgNamespacesToBeSearchedDefault = [ - NS_MAIN => true, - NS_PUBLIC => true, - NS_INTERNE => true, ]; # Debug From 0fcfe6da912d69a344a90bec9d8895ef2187b6c9 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sat, 23 May 2026 19:07:47 +0200 Subject: [PATCH 52/77] Add svg support --- hosts/vm/mediakiwi/mediawiki.nix | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 98dbdb1..0757885 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -1,4 +1,9 @@ -{ lib, pkgs, config, ... }: +{ + lib, + pkgs, + config, + ... +}: let version = pkgs.mediawiki.version; @@ -68,9 +73,10 @@ in # Files and Uploads $wgMaxUploadSize = 512 * 1024 * 1024; $wgUseImageMagick = true; - $wgImageMagickConvertCommand = '${ pkgs.imagemagick }/bin/convert'; + $wgImageMagickConvertCommand = '${pkgs.imagemagick}/bin/convert'; $wgStrictFileExtensions = false; $wgFileExtensions[] = 'pdf'; + $wgFileExtensions[] = 'svg'; # Limite mémoire, quand on fait de gros importi (9M de xml), le parser # prends plus que 50M @@ -149,10 +155,10 @@ in $wgCategoryGroupLockdown["!user"]["read"] = [ "Page Publique" ]; # Restrict read for non-user (i.e. anonymous) on execpt for Page Publique # Extensions $wgWikiEditorRealtimePreview = true; $wgCiteBookReferencing = true; - $wgPdfProcessor = '${ pkgs.ghostscript }/bin/gs'; + $wgPdfProcessor = '${pkgs.ghostscript}/bin/gs'; $wgPdfPostProcessor = $wgImageMagickConvertCommand; - $wgPdfInfo = '${ pkgs.poppler-utils }/bin/pdfinfo'; - $wgPdftoText = '${ pkgs.poppler-utils }/bin/pdftotext'; + $wgPdfInfo = '${pkgs.poppler-utils}/bin/pdfinfo'; + $wgPdftoText = '${pkgs.poppler-utils}/bin/pdftotext'; $wgScribuntoDefaultEngine = 'luasandbox'; # Custom Namespaces From e594c741b7e86ecbc41083bfdeb3f650237804b5 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sat, 13 Jun 2026 17:35:14 +0200 Subject: [PATCH 53/77] Update SHA and pass --- hosts/vm/mediakiwi/mediawiki.nix | 6 +++--- secrets/common/root.age | Bin 3054 -> 3164 bytes 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 0757885..6a5109e 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -182,7 +182,7 @@ in name = "Citizen"; owner = "StarCitizenTools"; repo = "mediawiki-skins-Citizen"; - tag = "v3.2.0"; + tag = "v3.17.0"; sha256 = "sha256-XtjYMBK2pINwbAzAKFJ+nWxcFXQd2E/Gqf3x+3Pa8VE="; }; }; @@ -225,7 +225,7 @@ in rev = "REL" + major + "_" + minor; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-WEg0QP4QXt89uvZCZvkfpycoZGBP640aTpaxbl5jsZs="; + sha256 = "sha256-lrBhevfXs1Eyi69uvF/+qs/+wzOsKm0SLbnY8lD6pp4="; }; patches = [ # Cette extension s’occupe des du contrôle d’accès du Wiki @@ -292,7 +292,7 @@ in rev = "REL" + major + "_" + minor; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-G2C/KuSgfWWftpHHhOABwFcOEb1zB8qmHi9KgrC6Jrc="; + sha256 = "sha256-04VCOKfrgrijrv9CdjXeaExmAsxIEWcWMz8W2KVutGI="; }; patches = [ "${./WSOAuth.patch}" ]; }; diff --git a/secrets/common/root.age b/secrets/common/root.age index e1b76db6051fe8861e9893134bf3833d693caa62..2df9ddc8f1d1018e9db898ca022f000322ca3996 100644 GIT binary patch literal 3164 zcmZXW%kTVpUB)47Om_zqLQXJ4+}Zr>Oz)M5^fuFWdcU{3-rMQ)HoZ<`;zmNEyA#6( zLI4dILr9dcfCSh83vr=w<3d@OqefSREgQa?92Vc-AMnZR^Sm$5`-zI|P1YulXFpY4 z`v={lfR+>R=Cg-!EZ<})jA1A00N0qR6$j+91-eo4xgDP*b7GB4irt_-vqTcfddS;Q zZLdo`DVYEeNbMQ=D0FcsPvt?xxDH_gC;N$T!Fw;QBLyv(e86+C(^3=nen%0B|? zXhe%o)`og2t>e7XHT}RlMH=dbR_IzA0*kYh;mFbAYg&noobLuQVvTNINnp@MPB6tWMS=LD6;z;D9wxH+6XAgw$rtD(F69Fmu?t z8v2^Hgrr-nhZ>}zovU^&rGuU}g>GFt&7fRJ^9qx_i#) z17G8oy4y|L((={7qh5V-O^?7e3hg1p3$j?ZT7Vd{tpILzcS1tdZZULUuKt440eW<# z-UG7$tF1CVEfCm}^(64GY3E6#`r<&xbBg7$Al?zOmrAg4sy$k-Y-8GCccnT!Cew7t zkl={|5LJM9Gl11`v?(DjS81+Xq`(D?)Z02j7|NW^@uuYcTa_&Bdn3_@OH(*8M~Yh3 zkOh+mGxc=LmAzH{q=JSld;G0@T!`|-uK<%oL9&As>q!?r+SMCa&C9%5+@N47hC+HW z*tWiRQ(nK;c3~6Orw{B|fc)+1h@e{MkH-^*#ZB1=&wA1t!d(`2*t1EWR)`w`Yq;5E zUL>&=2wAUe7u1iv?9$!y#Ua{3Dy4-6of>4c<7=8si^7RMELogKPj+g&+&J34=b5Ll zn9TWH->VA@%fYEaODV zHe^b@8V8vmZ=(DtYAQSG4%kwsV3bIaN8@m|gGCUaC6Amo|)jPj%3X^or9JsN9;qTA?lD`fAf`V89;101Na^OkF68tquc zp>nGz6teqO9kFhTs;D3h&W%mDXD-(l1B)KmHa@ge+yg+rTzx_b?5+;G+t46#!rI!9 zV61%O{TP>WI4Q zalfonpNrAnCHD}g!|X;eh~TiPt!4}em5)8H=j*xovWfGA`D zo)9KYPOie4QKoA*LDTT&qA5~FF~HntRwz}58pQn-Qmde0R{>a;%i+L`=3 z&owdEPgX&IxZ0=GWF8~)md^(=i=7y|M@x^&uo2~AP6-baQ|(2S78{qdj_waW5yQtYq+00hYyKO&=TrDebhbO0m}f zs6FWjbilcq2xqp{6MVMrmm0LV+L#wJW2y9(Im)m`Z<)tMEv?IXaHoFOsSy&7Yh5=9 znXmF7pU?L|o?vQ~u9~RGgpg(`8N#PEivyXG!?7~ma9ya+%6hV^ioaj;FA z7w}n4@IXdd%WX;=vf}e}pAiv^p>0yPw@e=^{Vp*MN26jO{pIpA3d11tskLrQ8Z?|i zdy`c}LBwJ1mY#^rJ_7BE#mfzSSw3~+J%$d8>|z@TtF$+>=$2?Nhm+GBLxFtjSjVQ| zx-aMRW%*ODtKg*@mcEElS`yg!sJwz*!v~rMDPer#E+jQ}g(Z5*SlBWCyNGt`H3I@)cV2~{xxF$^O?w?9@t z`Kzz~;a~pkee@cDipp6B6-itI^tNv`*9tlG=-?X>`A zX!GQg+hHi5WGRec5VeJCOx235v2SVpLeE}4VP=`T;yLwLHg4kS^?g(k1AqdDj%fjFcWN7pdIqf-~FddX)G@3fW7drdRs1D&cR zDRWRnKc-c7N@VnwR@F5rOc2QXOJUBd$tI)cbRD6mFv$lx%Em*{6N-n_8~wu3cwKQ& zpt6;Dh|gqZEpp|E@X~ju86v=@BV%M0_sL^gi4C4XZ>cmqsJR0Ju93BpZHP)3CqB!N z)r=0xNhhm*SGs2yrjHwtlKdT07H4u8hlL>XLw_aiEJaCVhEsugP^Ew;YQwOV$25TN z?%A?cbXi&bL$L>12-kh8wv*kCxvTD+QY{4fU5Kkww6urC*!aXP#sTXj9fSM z*fH;8xS0CUh|Ys@@n)CQI=g*Lqji_dAC$~f38;(Er8I|Iju)pin9LAJJh{tKWnH>@ z@{1IVFag}4i(Udcdi5BcFBn!5c6GcLjZP(nEZ1qBDw^{^CzsQMqaJyZ8;?|1Z2QwO zEm@Qdr8MSHHgUQNW>%-2&6e-<+?7|EK@XFnAjSrWXva*eowfG&h-4RZs)=)k-%v@K zhLwk5hH4p3!AkNemy~vM7U`|MH*mdA*2&yESnsdX2wqC_Sb7u(K}^>q`&-{0ZLluE zji6*X1KdSHEtF|Wk4~z{{S50XNW=%$0`?HSu=fVHefIHh{62AgdOO%7D3C!P3vJXv zw}MGastmJo-Zd3eB$P|yF$KXrYP1{SJlKq(*8I&*v%*Z>J;&RM%UW)kn9}(m9C)9v zlazFV$26Hf3J|>=Q6^IWePx#VBA8hysZ|xZUhK~V>kmUJ^xy$z*SPWNP2W?NdKGJL zHwcK5(Q(cYY<*q0Yk!-V+QWm`xmx__9+$_oK2z$&*v=P-ZCM{u2r33)^R#EM$q)E~ zcJ^KBabayya;vXx1+g2_Gv|UW{Ka;li#R|5n`t@0LF5?>=~E7J>iJesW8g1OCBW`) zn!IciV>JNoc5ld@^Nxk;sC(M7^0b@DS&LxI%FgF3t&QQ#ZiEX*Vz5VAM<5EMq)2?L zLi{F9@*B7F)cBCX11OuSAlCo=t*hoI9gN}IY}qHsvZW!wHgcP-*2`RLK%m%s(=L3b zyV6SaM?-6^@J?MlLKK6|1E@!K!N`XeuciZ2xbQ8Ad)u&h9CD;FqHp)8J5E>U|5^B6 z2J{Ekgct030N3*ob8Q^%4=d3Xy#NBTF-PLf4s@Fin4Tp@b91Jo_Ex)Bf5pKG=%t`9 zluB!=J+A?ztO9h$2L!{L82gm_GmUnvVjFV1%6@287zw7d%)CWx^C*s>BUa{3UoGIR ztYDJte$AS%PLXJo4vEkBCMxzQI&K_c$!a1# z%Pu)N8Jz7y(9YmXkwRf*#`PWOWWKDMAc_|LCh^Aoz+T*XBoZlrVF%N6w#H$YK-cLA^qWYf?Uiw_o3d3CJs$Jjql^F&G{I&7B6?zLem4cDv` z-AK^7YS>;##TMVet9@iiw3CWfTuR9IYUJ`-726A>EX0h~mpS#YpLmh%Z=**ue4uSg9s(3Rh(nYBS8zN#A1;JA|T>?v9ni z7kqn;P)B$H~MNzkd4{-~AZ+?N?ua7Wo&W{r<~e{Q7&D_WEba zkH7uem;dz46ZP58KJ&9btIyIeE1!Df2mkoVhf3vpZ$9&ZFMZ`wv#w=~@5) From 409591817859df23ffcb34a0e2fc5e854f3dbbd8 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sat, 13 Jun 2026 18:56:37 +0200 Subject: [PATCH 54/77] Update extensions --- hosts/vm/mediakiwi/category-lockdown.patch | 18 +++++++++++----- hosts/vm/mediakiwi/mediawiki.nix | 24 +++++++--------------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/hosts/vm/mediakiwi/category-lockdown.patch b/hosts/vm/mediakiwi/category-lockdown.patch index 3a2b092..185ab41 100644 --- a/hosts/vm/mediakiwi/category-lockdown.patch +++ b/hosts/vm/mediakiwi/category-lockdown.patch @@ -1,8 +1,16 @@ diff --git a/CategoryLockdown.php b/CategoryLockdown.php -index 1e17ec7..caa13c1 100644 +index 3309c52..f2cef51 100644 --- a/CategoryLockdown.php +++ b/CategoryLockdown.php -@@ -16,6 +16,8 @@ class CategoryLockdown { +@@ -1,6 +1,7 @@ + getUserGroupManager()->getUserGroups( $user ); $implicitGroups = MediaWikiServices::getInstance()->getUserGroupManager()->getUserImplicitGroups( $user ); -@@ -26,6 +28,11 @@ class CategoryLockdown { +@@ -25,6 +28,11 @@ class CategoryLockdown { return; } @@ -23,7 +31,7 @@ index 1e17ec7..caa13c1 100644 $categories = array_keys( $title->getParentCategories() ); // Apply rules to the category page itself -@@ -33,16 +40,11 @@ class CategoryLockdown { +@@ -32,16 +40,11 @@ class CategoryLockdown { $categories[] = $title->getFullText(); } @@ -42,7 +50,7 @@ index 1e17ec7..caa13c1 100644 if ( !$permissions ) { continue; } -@@ -57,15 +59,40 @@ class CategoryLockdown { +@@ -56,15 +59,40 @@ class CategoryLockdown { $combinedGroups[] = $allowedGroup; } } diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 6a5109e..7ea188e 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -210,7 +210,7 @@ in Scribunto = null; # pour faire du scripting et des templates pour le wiki SyntaxHighlight_GeSHi = null; # pour avoir de la coloration syntaxique TemplateData = null; # pour faire de la doc sur les templates - # TemplateStyles = null; # pour styliser les templates (bundlé pour la prochaine version de mediawiki) + TemplateStyles = null; # pour styliser les templates (bundlé pour la prochaine version de mediawiki) TextExtracts = null; # pour set le bon text représentant un article Thanks = null; # pour remercier des gens en notif TitleBlacklist = null; # pour faire de la modération @@ -235,16 +235,6 @@ in ]; }; - TemplateStyles = pkgs.fetchFromGitHub { - name = "TemplateStyles"; - owner = "wikimedia"; - repo = "mediawiki-extensions-TemplateStyles"; - rev = "REL" + major + "_" + minor; - # Le SHA doit être changé à chaque nouveau commit de traduction. - # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-fCBccHTIBJrUyxe6oP7VR84HO65ICETE73X4rswQo4c="; - }; - # Popups Popups = pkgs.fetchFromGitHub { name = "Popups"; @@ -253,7 +243,7 @@ in rev = "REL" + major + "_" + minor; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-Vn/XGVYvM5doPtTEONESdVhgZlH/Fku74MeQbGrwU/E="; + sha256 = "sha256-wNI6LnpGrpUA96Nr+4+KbuislKXTGyyua2F3N+t2O1s="; }; # Auth @@ -264,25 +254,25 @@ in rev = "REL" + major + "_" + minor; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-3+nzeWemVAHGmLz3ZMvDSvP2UCmsnEiGJcE/oEakr2s="; + sha256 = "sha256-ZGw0Wgz0Sg04YDcOzkOGywmfQ6s6Ex17QbjmUDO1D8c="; }; LDAPProvider = pkgs.fetchFromGitHub { name = "LDAPProvider"; owner = "wikimedia"; repo = "mediawiki-extensions-LDAPProvider"; - rev = "REL" + major + "_" + minor; + tag = "3.1.0"; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-bpj+MS4XKLoDdtEK+Mv7Ifa6qz215jKhSL1DOOw4ZPs="; + sha256 = "sha256-9/5Uph5C6VPWT7NzrPsYpM9pxZPIQneKZDPFCaYm1e8="; }; LDAPAuthentication2 = pkgs.fetchFromGitHub { name = "LDAPAuthentication2"; owner = "wikimedia"; repo = "mediawiki-extensions-LDAPAuthentication2"; - rev = "REL" + major + "_" + minor; + tag = "3.1.0"; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-oi5rliHb4KnLbvQxO7MGuLp/FEucoGR/Z0NP1gmbgMc="; + sha256 = "sha256-/9VISvpvhaVq/sAHE10INlTju7+PTirFO/6U68WBOgM="; }; WSOAuth = pkgs.applyPatches { src = pkgs.fetchFromGitHub { From 7c859a29875c5a094f3cd5845076bae7ec9e0624 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sat, 13 Jun 2026 18:58:11 +0200 Subject: [PATCH 55/77] Update skin --- hosts/vm/mediakiwi/mediawiki.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 7ea188e..446e13e 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -183,7 +183,7 @@ in owner = "StarCitizenTools"; repo = "mediawiki-skins-Citizen"; tag = "v3.17.0"; - sha256 = "sha256-XtjYMBK2pINwbAzAKFJ+nWxcFXQd2E/Gqf3x+3Pa8VE="; + sha256 = "sha256-IPwfI2ldzis3Zni5QSsrSfi0Qt6+xjOupPJk+j7P6aM="; }; }; From 26cb0339603da4afc8e894da01649b32264e267e Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sun, 5 Jul 2026 13:59:28 +0200 Subject: [PATCH 56/77] Fix syntaxhighlight --- hosts/vm/mediakiwi/mediawiki.nix | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 446e13e..1b364d1 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -33,6 +33,9 @@ in # For the PdfHandler extension ghostscript poppler-utils + + # For the SyntaxHighlighting extension + python3Packages.pygments ]; services.phpfpm.pools.mediawiki.phpOptions = '' @@ -161,6 +164,9 @@ in $wgPdftoText = '${pkgs.poppler-utils}/bin/pdftotext'; $wgScribuntoDefaultEngine = 'luasandbox'; + # SyntaxHighlight extension + $wgPygmentizePath = '${pkgs.python3Packages.pygments}/bin/pygmentize'; + # Custom Namespaces define("NS_ARCHIVE", 3000); define("NS_ARCHIVE_TALK", 3001); From 1520bd6d4cd237d381f7d2461457464cdbcb8a60 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Wed, 22 Jul 2026 12:31:26 +0200 Subject: [PATCH 57/77] =?UTF-8?q?fix:=20cr=C3=A9ation=20compte=20via=20LDA?= =?UTF-8?q?P/Note?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hosts/vm/mediakiwi/category-lockdown.patch | 10 +++++++--- hosts/vm/mediakiwi/mediawiki.nix | 12 ++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/hosts/vm/mediakiwi/category-lockdown.patch b/hosts/vm/mediakiwi/category-lockdown.patch index 185ab41..e64c1bb 100644 --- a/hosts/vm/mediakiwi/category-lockdown.patch +++ b/hosts/vm/mediakiwi/category-lockdown.patch @@ -1,5 +1,5 @@ diff --git a/CategoryLockdown.php b/CategoryLockdown.php -index 3309c52..f2cef51 100644 +index 3309c52..c4f4cb0 100644 --- a/CategoryLockdown.php +++ b/CategoryLockdown.php @@ -1,6 +1,7 @@ @@ -50,7 +50,7 @@ index 3309c52..f2cef51 100644 if ( !$permissions ) { continue; } -@@ -56,15 +59,40 @@ class CategoryLockdown { +@@ -56,15 +59,44 @@ class CategoryLockdown { $combinedGroups[] = $allowedGroup; } } @@ -80,7 +80,11 @@ index 3309c52..f2cef51 100644 + continue; # Skip if this group rule doesn’t match the user + } + -+ $requiredCat = $groupCategories[$action] ?? []; ++ $requiredCat = $groupCategories[$action]; ++ if ( $requiredCat == null ) { ++ continue; ++ } ++ + $groupLocked = true; + foreach ( $requiredCat as $c ) { + if ( in_array( $c, $categories ) ) { diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 1b364d1..94043ea 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -77,8 +77,8 @@ in $wgMaxUploadSize = 512 * 1024 * 1024; $wgUseImageMagick = true; $wgImageMagickConvertCommand = '${pkgs.imagemagick}/bin/convert'; - $wgStrictFileExtensions = false; - $wgFileExtensions[] = 'pdf'; + $wgStrictFileExtensions = false; # On autorise toutes les extensions sauf https://www.mediawiki.org/wiki/Manual:$wgProhibitedFileExtensions + $wgFileExtensions[] = 'pdf'; $wgFileExtensions[] = 'svg'; # Limite mémoire, quand on fait de gros importi (9M de xml), le parser @@ -145,7 +145,7 @@ in # Access Control $wgGroupPermissions['*']['edit'] = false; # Restrict edition for anonymous user $wgGroupPermissions['*']['createaccount'] = false; # Restrict the creation of account to sysop only - + $wgGroupPermissions['*']['autocreateaccount'] = true; # Création de comptes depuis LDAP et Note mais pas local $wgCategoryLockdownWhitelist = [ "Spécial:Connexion", "Spécial:Connexion/return", @@ -154,8 +154,9 @@ in "MediaWiki:Common.css", "MediaWiki:Common.js" ]; + $wgCategoryGroupLockdown["!user"]["read"] = [ "Page Publique" ]; # Restrict read for non-user (i.e. anonymous) on execpt for Page Publique - $wgCategoryGroupLockdown["!user"]["read"] = [ "Page Publique" ]; # Restrict read for non-user (i.e. anonymous) on execpt for Page Publique # Extensions + # Extensions $wgWikiEditorRealtimePreview = true; $wgCiteBookReferencing = true; $wgPdfProcessor = '${pkgs.ghostscript}/bin/gs'; @@ -174,6 +175,9 @@ in NS_ARCHIVE => "Archive", NS_ARCHIVE_TALK => "Discussion_archive", ]; + $wgNamespacesWithSubpages[NS_MAIN] = true; + $wgNamespacesWithSubpages[NS_ARCHIVE] = true; + $wgVisualEditorAvailableNamespaces[NS_ARCHIVE] = true; # Debug $wgShowExceptionDetails = true; From ffda38b4f134d31013c46f770ec1f831eda16703 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Fri, 24 Jul 2026 11:32:26 +0200 Subject: [PATCH 58/77] Emails, and fix acls --- hosts/vm/mediakiwi/mediawiki.nix | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 94043ea..5d852f5 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -87,25 +87,22 @@ in # E-mail settings $wgEnableEmail = true; - # $wgSMTP = [ - # 'host' => 'smtp.adm.crans.org', - # 'IDHost' => 'crans.org', - # 'localhost' => 'crans.org', - # 'port' => , - # 'auth' => false, - # # 'username' => , - # # 'password' => , - # ]; + $wgSMTP = [ + 'host' => 'smtp.adm.crans.org', + 'IDHost' => 'crans.org', + 'localhost' => 'crans.org', + 'port' => 25, + 'auth' => false, + ]; $wgPasswordSender = 'root@crans.org'; $wgEmergencyContact = 'contact@crans.org'; $wgNoReplyAddress = 'root@crans.org'; $wgEnableUserEmail = false; - # $wgEnableSpecialMute = true; $wgAllowHTMLEmail = true; - $wgEnotifUseRealName = false; - $wgEnotifFromEditor = false; - $wgEnotifRevealEditorAddress = false; + $wgEnotifUseRealName = true; # On active les noms réels pour plus de lisibilité avec les comptes note + $wgEnotifFromEditor = false; # On ne se fait pas passer par l’utilisateurice + $wgEnotifRevealEditorAddress = false; # Pas de champ répondre avec l’adresse mail de l’utilisateurice $wgEnotifUserTalk = true; $wgEnotifMinorEdits = true; $wgEnotifWatchlist = true; @@ -146,6 +143,7 @@ in $wgGroupPermissions['*']['edit'] = false; # Restrict edition for anonymous user $wgGroupPermissions['*']['createaccount'] = false; # Restrict the creation of account to sysop only $wgGroupPermissions['*']['autocreateaccount'] = true; # Création de comptes depuis LDAP et Note mais pas local + $wgGroupPermissions['*']['delete'] = true; # Création de comptes depuis LDAP et Note mais pas local $wgCategoryLockdownWhitelist = [ "Spécial:Connexion", "Spécial:Connexion/return", @@ -155,6 +153,7 @@ in "MediaWiki:Common.js" ]; $wgCategoryGroupLockdown["!user"]["read"] = [ "Page Publique" ]; # Restrict read for non-user (i.e. anonymous) on execpt for Page Publique + $wgFeed = false; # Pas de flux car ils ne sont pas authentifiés :(. # Extensions $wgWikiEditorRealtimePreview = true; From 924f92ad00fc347bccf5d8d6e1ca0a008eefda3a Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sat, 25 Jul 2026 19:11:05 +0200 Subject: [PATCH 59/77] =?UTF-8?q?mise=20=C3=A0=20jour=20mw=20et=20extensio?= =?UTF-8?q?n=20FullCalendar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FullCalendar/FullCalendarMagic.i18n.php | 9 + .../vm/mediakiwi/FullCalendar/extension.json | 45 + .../FullCalendar/includes/FullCalendar.php | 21 + .../resources/fullcalendar7/LICENSE.md | 22 + .../fullcalendar7/fullcalendar-ical.js | 237 + .../fullcalendar7/fullcalendar.global.js | 17479 ++++++++++++++++ .../fullcalendar7/locales-all/global.js | 1728 ++ .../resources/fullcalendar7/skeleton.css | 469 + .../fullcalendar7/themes/breezy/global.js | 387 + .../themes/breezy/palettes/rose.css | 90 + .../fullcalendar7/themes/breezy/theme.css | 1093 + .../FullCalendar/resources/ical.es5.cjs | 9717 +++++++++ .../mediakiwi/FullCalendar/resources/init.js | 24 + hosts/vm/mediakiwi/category-lockdown.patch | 8 - hosts/vm/mediakiwi/mediawiki.nix | 7 +- 15 files changed, 31326 insertions(+), 10 deletions(-) create mode 100644 hosts/vm/mediakiwi/FullCalendar/FullCalendarMagic.i18n.php create mode 100644 hosts/vm/mediakiwi/FullCalendar/extension.json create mode 100644 hosts/vm/mediakiwi/FullCalendar/includes/FullCalendar.php create mode 100644 hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/LICENSE.md create mode 100644 hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/fullcalendar-ical.js create mode 100644 hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/fullcalendar.global.js create mode 100644 hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/locales-all/global.js create mode 100644 hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/skeleton.css create mode 100644 hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/global.js create mode 100644 hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/palettes/rose.css create mode 100644 hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/theme.css create mode 100644 hosts/vm/mediakiwi/FullCalendar/resources/ical.es5.cjs create mode 100644 hosts/vm/mediakiwi/FullCalendar/resources/init.js diff --git a/hosts/vm/mediakiwi/FullCalendar/FullCalendarMagic.i18n.php b/hosts/vm/mediakiwi/FullCalendar/FullCalendarMagic.i18n.php new file mode 100644 index 0000000..b2ed884 --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/FullCalendarMagic.i18n.php @@ -0,0 +1,9 @@ + [ 0, 'calendar' ], + ]; + $magicWords['fr'] = [ + 'calendar' => [ 0, 'calendrier' ], + ]; +?> diff --git a/hosts/vm/mediakiwi/FullCalendar/extension.json b/hosts/vm/mediakiwi/FullCalendar/extension.json new file mode 100644 index 0000000..8315c2f --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/extension.json @@ -0,0 +1,45 @@ +{ + "name": "FullCalendar", + "version": "0.1.0", + "author": [ + "Crans" + ], + "url": "", + "license-name": "GPL-3.0+", + "description": "", + "type": "parserhook", + "requires": { + "MediaWiki": ">= 1.39.0" + }, + "ResourceModules": { + "ext.fullcalendar": { + "scripts": [ + "ical.es5.cjs", + "fullcalendar7/fullcalendar.global.js", + "fullcalendar7/themes/breezy/global.js", + "fullcalendar7/locales-all/global.js", + "fullcalendar7/fullcalendar-ical.js", + "init.js" + ], + "styles": [ + "fullcalendar7/skeleton.css", + "fullcalendar7/themes/breezy/theme.css", + "fullcalendar7/themes/breezy/palettes/rose.css" + ] + } + }, + "ResourceFileModulePaths": { + "localBasePath": "resources", + "remoteExtPath": "fullcalendar/resources" + }, + "AutoloadClasses": { + "FullCalendar": "includes/FullCalendar.php" + }, + "ExtensionMessagesFiles": { + "FullCalendarMagic": "FullCalendarMagic.i18n.php" + }, + "Hooks": { + "ParserFirstCallInit": "FullCalendar::onParserFirstCallInit" + }, + "manifest_version": 2 +} diff --git a/hosts/vm/mediakiwi/FullCalendar/includes/FullCalendar.php b/hosts/vm/mediakiwi/FullCalendar/includes/FullCalendar.php new file mode 100644 index 0000000..90937e2 --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/includes/FullCalendar.php @@ -0,0 +1,21 @@ +example magic word with renderExample() + $parser->setFunctionHook( 'calendar', [ self::class, 'renderCalendar' ] ); + } + + public static function renderCalendar( Parser $parser, $param1 = '', ) { + $parser->getOutput()->addModules( [ 'ext.fullcalendar' ] ); + + $resultHtml = Html::element( 'div', [ 'class' => 'mw-crans-fullcalendar', 'data-ics' => $param1 ] ); + return [ $resultHtml, 'noparse' => true, 'isHTML' => true ]; + } + +} +?> diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/LICENSE.md b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/LICENSE.md new file mode 100644 index 0000000..c07376a --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/LICENSE.md @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2026 Adam Shaw + +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. diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/fullcalendar-ical.js b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/fullcalendar-ical.js new file mode 100644 index 0000000..33b4a11 --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/fullcalendar-ical.js @@ -0,0 +1,237 @@ +/*! +FullCalendar iCalendar Plugin v7.0.1 +Docs & License: https://fullcalendar.io/docs/icalendar +(c) 2026 Adam Shaw +*/ +(function (ICAL) { + 'use strict'; + + function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } + + var ICAL__default = /*#__PURE__*/_interopDefaultLegacy(ICAL); + + function addDays(m, n) { + let a = dateToUtcArray(m); + a[2] += n; + return arrayToUtcDate(a); + } + function dateToUtcArray(date) { + return [ + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds(), + ]; + } + function arrayToUtcDate(a) { + // according to web standards (and Safari), a month index is required. + // massage if only given a year. + if (a.length === 1) { + a = a.concat([0]); + } + return new Date(Date.UTC(...a)); + } + + /* eslint-disable */ + class IcalExpander { + constructor(opts) { + this.maxIterations = opts.maxIterations != null ? opts.maxIterations : 1000; + this.skipInvalidDates = opts.skipInvalidDates != null ? opts.skipInvalidDates : false; + this.jCalData = ICAL__default["default"].parse(opts.ics); + this.component = new ICAL__default["default"].Component(this.jCalData); + this.events = this.component.getAllSubcomponents('vevent').map(vevent => new ICAL__default["default"].Event(vevent)); + if (this.skipInvalidDates) { + this.events = this.events.filter((evt) => { + try { + evt.startDate.toJSDate(); + evt.endDate.toJSDate(); + return true; + } + catch (err) { + // skipping events with invalid time + return false; + } + }); + } + } + between(after, before) { + function isEventWithinRange(startTime, endTime) { + return (!after || endTime >= after.getTime()) && + (!before || startTime <= before.getTime()); + } + function getTimes(eventOrOccurrence) { + const startTime = eventOrOccurrence.startDate.toJSDate().getTime(); + let endTime = eventOrOccurrence.endDate.toJSDate().getTime(); + // If it is an all day event, the end date is set to 00:00 of the next day + // So we need to make it be 23:59:59 to compare correctly with the given range + if (eventOrOccurrence.endDate.isDate && (endTime > startTime)) { + endTime -= 1; + } + return { startTime, endTime }; + } + const exceptions = []; + this.events.forEach((event) => { + if (event.isRecurrenceException()) + exceptions.push(event); + }); + const ret = { + events: [], + occurrences: [], + }; + this.events.filter(e => !e.isRecurrenceException()).forEach((event) => { + const exdates = []; + event.component.getAllProperties('exdate').forEach((exdateProp) => { + const exdate = exdateProp.getFirstValue(); + exdates.push(exdate.toJSDate().getTime()); + }); + // Recurring event is handled differently + if (event.isRecurring()) { + const iterator = event.iterator(); + let next; + let i = 0; + do { + i += 1; + next = iterator.next(); + if (next) { + const occurrence = event.getOccurrenceDetails(next); + const { startTime, endTime } = getTimes(occurrence); + const isOccurrenceExcluded = exdates.indexOf(startTime) !== -1; + // TODO check that within same day? + const exception = exceptions.find(ex => ex.uid === event.uid && ex.recurrenceId.toJSDate().getTime() === occurrence.startDate.toJSDate().getTime()); + // We have passed the max date, stop + if (before && startTime > before.getTime()) + break; + // Check that we are within our range + if (isEventWithinRange(startTime, endTime)) { + if (exception) { + ret.events.push(exception); + } + else if (!isOccurrenceExcluded) { + ret.occurrences.push(occurrence); + } + } + } + } while (next && (!this.maxIterations || i < this.maxIterations)); + return; + } + // Non-recurring event: + const { startTime, endTime } = getTimes(event); + if (isEventWithinRange(startTime, endTime)) + ret.events.push(event); + }); + return ret; + } + before(before) { + return this.between(undefined, before); + } + after(after) { + return this.between(after); + } + all() { + return this.between(); + } + } + + const eventSourceDef = { + parseMeta(refined) { + if (refined.url && refined.format === 'ics') { + return { + url: refined.url, + format: 'ics', + }; + } + return null; + }, + fetch(arg, successCallback, // any + errorCallback) { + let meta = arg.eventSource.meta; + let { internalState } = meta; + /* + NOTE: isRefetch is a HACK. we would do the recurring-expanding in a separate plugin hook, + but we couldn't leverage built-in allDay-guessing, among other things. + */ + if (!internalState || arg.isRefetch) { + internalState = meta.internalState = { + response: null, + iCalExpanderPromise: fetch(meta.url, { method: 'GET' }).then((response) => { + return response.text().then((icsText) => { + internalState.response = response; + return new IcalExpander({ + ics: icsText, + skipInvalidDates: true, + }); + }); + }), + }; + } + internalState.iCalExpanderPromise.then((iCalExpander) => { + successCallback({ + rawEvents: expandICalEvents(iCalExpander, arg.range), + response: internalState.response, + }); + }, errorCallback); + }, + }; + function expandICalEvents(iCalExpander, range) { + // expand the range. because our `range` is timeZone-agnostic UTC + // or maybe because ical.js always produces dates in local time? i forget + let rangeStart = addDays(range.start, -1); + let rangeEnd = addDays(range.end, 1); + let iCalRes = iCalExpander.between(rangeStart, rangeEnd); // end inclusive. will give extra results + let expanded = []; + // TODO: instead of using startDate/endDate.toString to communicate allDay, + // we can query startDate/endDate.isDate. More efficient to avoid formatting/reparsing. + // single events + for (let iCalEvent of iCalRes.events) { + expanded.push({ + ...buildNonDateProps(iCalEvent), + start: iCalEvent.startDate.toString(), + end: (specifiesEnd(iCalEvent) && iCalEvent.endDate) + ? iCalEvent.endDate.toString() + : null, + }); + } + // recurring event instances + for (let iCalOccurence of iCalRes.occurrences) { + let iCalEvent = iCalOccurence.item; + expanded.push({ + ...buildNonDateProps(iCalEvent), + start: iCalOccurence.startDate.toString(), + end: (specifiesEnd(iCalEvent) && iCalOccurence.endDate) + ? iCalOccurence.endDate.toString() + : null, + }); + } + return expanded; + } + function buildNonDateProps(iCalEvent) { + return { + title: iCalEvent.summary, + url: extractEventUrl(iCalEvent), + extendedProps: { + location: iCalEvent.location, + organizer: iCalEvent.organizer, + description: iCalEvent.description, + }, + }; + } + function extractEventUrl(iCalEvent) { + let urlProp = iCalEvent.component.getFirstProperty('url'); + return urlProp ? urlProp.getFirstValue() : ''; + } + function specifiesEnd(iCalEvent) { + return Boolean(iCalEvent.component.getFirstProperty('dtend')) || + Boolean(iCalEvent.component.getFirstProperty('duration')); + } + + var plugin = { + name: 'icalendar', + eventSourceDefs: [eventSourceDef], + }; + + FullCalendar.globalPlugins.push(plugin); + +})(ICAL); diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/fullcalendar.global.js b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/fullcalendar.global.js new file mode 100644 index 0000000..e8f6393 --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/fullcalendar.global.js @@ -0,0 +1,17479 @@ +/*! +FullCalendar (Vanilla JS) v7.0.1 +Docs & License: https://fullcalendar.io +(c) 2026 Adam Shaw +*/ +var FullCalendar = (function (exports) { + 'use strict'; + + var n$1,l$2,u$2,t$1,i$2,r$1,o$1,e$1,f$2,c$2,s$2,a$2,h,p$1,v$1,y,d={},w$1=[],_=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,g$1=Array.isArray;function m$1(n,l){for(var u in l)n[u]=l[u];return n}function b(n){n&&n.parentNode&&n.parentNode.removeChild(n);}function k$1(l,u,t){var i,r,o,e={};for(o in u)"key"==o?i=u[o]:"ref"==o?r=u[o]:e[o]=u[o];if(arguments.length>2&&(e.children=arguments.length>3?n$1.call(arguments,2):t),"function"==typeof l&&null!=l.defaultProps)for(o in l.defaultProps)void 0===e[o]&&(e[o]=l.defaultProps[o]);return x(l,e,i,r,null)}function x(n,t,i,r,o){var e={type:n,props:t,key:i,ref:r,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:null==o?++u$2:o,__i:-1,__u:0};return null==o&&null!=l$2.vnode&&l$2.vnode(e),e}function M$1(){return {current:null}}function S(n){return n.children}function C(n,l){this.props=n,this.context=l;}function $$1(n,l){if(null==l)return n.__?$$1(n.__,n.__i+1):null;for(var u;ll&&i$2.sort(e$1),n=i$2.shift(),l=i$2.length,I(n);}finally{i$2.length=H$1.__r=0;}}function L(n,l,u,t,i,r,o,e,f,c,s){var a,h,p,v,y,_,g,m=t&&t.__k||w$1,b=l.length;for(f=T$1(u,l,m,f,b),a=0;a0?o=n.__k[r]=x(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):n.__k[r]=o,f=r+h,o.__=n,o.__b=n.__b+1,e=null,-1!=(c=o.__i=O$1(o,u,f,a))&&(a--,(e=u[c])&&(e.__u|=2)),null==e||null==e.__v?(-1==c&&(i>s?h--:if?h--:h++,o.__u|=4))):n.__k[r]=null;if(a)for(r=0;r(s?1:0))for(i=u-1,r=u+1;i>=0||r=0?i--:r++])&&0==(2&c.__u)&&e==c.key&&f==c.type)return o;return -1}function z$1(n,l,u){"-"==l[0]?n.setProperty(l,null==u?"":u):n[l]=null==u?"":"number"!=typeof u||_.test(l)?u:u+"px";}function N(n,l,u,t,i){var r,o;n:if("style"==l)if("string"==typeof u)n.style.cssText=u;else {if("string"==typeof t&&(n.style.cssText=t=""),t)for(l in t)u&&l in u||z$1(n.style,l,"");if(u)for(l in u)t&&u[l]==t[l]||z$1(n.style,l,u[l]);}else if("o"==l[0]&&"n"==l[1])r=l!=(l=l.replace(a$2,"$1")),o=l.toLowerCase(),l=o in n||"onFocusOut"==l||"onFocusIn"==l?o.slice(2):l.slice(2),n.l||(n.l={}),n.l[l+r]=u,u?t?u[s$2]=t[s$2]:(u[s$2]=h,n.addEventListener(l,r?v$1:p$1,r)):n.removeEventListener(l,r?v$1:p$1,r);else {if("http://www.w3.org/2000/svg"==i)l=l.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if("width"!=l&&"height"!=l&&"href"!=l&&"list"!=l&&"form"!=l&&"tabIndex"!=l&&"download"!=l&&"rowSpan"!=l&&"colSpan"!=l&&"role"!=l&&"popover"!=l&&l in n)try{n[l]=null==u?"":u;break n}catch(n){}"function"==typeof u||(null==u||!1===u&&"-"!=l[4]?n.removeAttribute(l):n.setAttribute(l,"popover"==l&&1==u?"":u));}}function V$1(n){return function(u){if(this.l){var t=this.l[u.type+n];if(null==u[c$2])u[c$2]=h++;else if(u[c$2]0?n:g$1(n)?n.map(E$1):m$1({},n)}function G$1(u,t,i,r,o,e,f,c,s){var a,h,p,v,y,w,_,m=i.props||d,k=t.props,x=t.type;if("svg"==x?o="http://www.w3.org/2000/svg":"math"==x?o="http://www.w3.org/1998/Math/MathML":o||(o="http://www.w3.org/1999/xhtml"),null!=e)for(a=0;a2&&(f.children=arguments.length>3?n$1.call(arguments,2):t),x(l.type,f,i||l.key,r||l.ref,null)}function X$1(n){function l(n){var u,t;return this.getChildContext||(u=new Set,(t={})[l.__c]=this,this.getChildContext=function(){return t},this.componentWillUnmount=function(){u=null;},this.shouldComponentUpdate=function(n){this.props.value!=n.value&&u.forEach(function(n){n.__e=!0,A(n);});},this.sub=function(n){u.add(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){u&&u.delete(n),l&&l.call(n);};}),n.children}return l.__c="__cC"+y++,l.__=n,l.Provider=l.__l=(l.Consumer=function(n,l){return n.children(l)}).contextType=l,l}n$1=w$1.slice,l$2={__e:function(n,l,u,t){for(var i,r,o;l=l.__;)if((i=l.__c)&&!i.__)try{if((r=i.constructor)&&null!=r.getDerivedStateFromError&&(i.setState(r.getDerivedStateFromError(n)),o=i.__d),null!=i.componentDidCatch&&(i.componentDidCatch(n,t||{}),o=i.__d),o)return i.__E=i}catch(l){n=l;}throw n}},u$2=0,t$1=function(n){return null!=n&&void 0===n.constructor},C.prototype.setState=function(n,l){var u;u=null!=this.__s&&this.__s!=this.state?this.__s:this.__s=m$1({},this.state),"function"==typeof n&&(n=n(m$1({},u),this.props)),n&&m$1(u,n),null!=n&&this.__v&&(l&&this._sb.push(l),A(this));},C.prototype.forceUpdate=function(n){this.__v&&(this.__e=!0,n&&this.__h.push(n),A(this));},C.prototype.render=S,i$2=[],o$1="function"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,e$1=function(n,l){return n.__v.__b-l.__v.__b},H$1.__r=0,f$2=Math.random().toString(8),c$2="__d"+f$2,s$2="__a"+f$2,a$2=/(PointerCapture)$|Capture$/i,h=0,p$1=V$1(!1),v$1=V$1(!0),y=0; + + var preact = /*#__PURE__*/Object.freeze({ + __proto__: null, + Component: C, + Fragment: S, + cloneElement: W$1, + createContext: X$1, + createElement: k$1, + createRef: M$1, + h: k$1, + hydrate: U$1, + get isValidElement () { return t$1; }, + get options () { return l$2; }, + render: R, + toChildArray: F + }); + + var t=/["&<]/;function n(r){if(0===r.length||!1===t.test(r))return r;for(var e=0,n=0,o="",f="";n `Non-finite ${entityName}: ${num}`; + const forbiddenBigIntToNumber = (entityName) => `Cannot convert bigint to ${entityName}`; + const invalidObject = 'Invalid object'; + const numberOutOfRange = (entityName, val, min, max) => invalidEntity$1(entityName, val) + `; must be between ${min}-${max}`; + // Entity/Fields/Bags + const invalidEntity$1 = (fieldName, val) => `Invalid ${fieldName}: ${val}`; + + const nanoInMicro$1 = 1000; + const nanoInMilli$1 = 1000000; + const nanoInSec$1 = 1000000000; + const nanoInMinute$1 = 60000000000; + const nanoInHour$1 = 3600000000000; + function normalizeOptions(options) { + if (options === undefined) { + return Object.create(null); + } + return requireObjectLike(options); + } + function toFiniteNumber(arg, entityName = 'number') { + if (typeof arg === 'bigint') { + throw new TypeError(forbiddenBigIntToNumber(entityName)); + } + arg = Number(arg); + if (!Number.isFinite(arg)) { + throw new RangeError(expectedFinite(entityName, arg)); + } + return arg; + } + function toIntegerWithTrunc(arg, entityName) { + return Math.trunc(toFiniteNumber(arg, entityName)) || 0; // ensure no -0 + } + /* + min/max are inclusive + */ + function constrainToRange$1(num, min, max) { + return Math.min(Math.max(num, min), max); + } + function isObjectLike(arg) { + return arg !== null && (typeof arg === 'object' || typeof arg === 'function'); + } + function requireObjectLike(arg) { + if (!isObjectLike(arg)) { + throw new TypeError(invalidObject); + } + return arg; + } + + const invalidEntity = invalidEntity$1; + + const missingField = fieldName => `Missing ${fieldName}`; + + const invalidChoice = (fieldName, val, choiceMap) => invalidEntity$1(fieldName, val) + "; must be " + Object.keys(choiceMap).join(); + + const forbiddenValueOf$1 = "Cannot use valueOf"; + + const invalidCallingContext = "Invalid calling context"; + + const exoticCalendarRequired = (calendarId, remedy) => `Unknown calendar ${calendarId}; might need ${remedy}`; + + const invalidTimeZone = calendarId => invalidEntity$1("TimeZone", calendarId); + + const outOfBoundsDate = "Out-of-bounds date"; + + const invalidSubstring = substring => `Invalid substring: ${substring}`; + + const constrainToRange = constrainToRange$1; + + function throwRangeError(message) { + throw new RangeError(message); + } + + function throwTypeError(message) { + throw new TypeError(message); + } + + function clampProp(props, propName, min, max, overflow) { + return clampEntity(propName, ((props, propName) => { + const propVal = props[propName]; + return void 0 === propVal && throwTypeError(missingField(propName)), propVal; + })(props, propName), min, max, overflow); + } + + function clampEntity(entityName, num, min, max, overflow, choices) { + const clamped = constrainToRange(num, min, max); + return overflow && num !== clamped && throwRangeError(((entityName, val, min, max, choices) => choices ? numberOutOfRange(entityName, choices[val], choices[min], choices[max]) : numberOutOfRange(entityName, val, min, max))(entityName, num, min, max, choices)), + clamped; + } + + function memoize$1(generator, MapClass = Map) { + const map = new MapClass; + return (key, ...otherArgs) => { + if (map.has(key)) { + return map.get(key); + } + const val = generator(key, ...otherArgs); + return map.set(key, val), val; + }; + } + + const createNameDescriptors = name => createPropDescriptors({ + name: name + }, 1); + + const createPropDescriptors = (propVals, readonly) => mapProps(value => ({ + value: value, + configurable: 1, + writable: !readonly + }), propVals); + + const createStringTagDescriptors = value => ({ + [Symbol.toStringTag]: { + value: value, + configurable: 1 + } + }); + + function mapProps(transformer, props) { + const res = {}; + for (const propName in props) { + res[propName] = transformer(props[propName], propName); + } + return res; + } + + function createPropGetters(propNames) { + const getters = {}; + for (const propName of propNames) { + getters[propName] = slots => slots[propName]; + } + return getters; + } + + function pluckProps(propNames, props, dest = Object.create(null)) { + for (const propName of propNames) { + dest[propName] = props[propName]; + } + return dest; + } + + function bindArgs(f, ...boundArgs) { + return (...dynamicArgs) => f(...boundArgs, ...dynamicArgs); + } + + function noop() {} + + function capitalize(s) { + return s[0].toUpperCase() + s.substring(1); + } + + function createRegExp(meat) { + return new RegExp(`^${meat}$`, "i"); + } + + function parseSubsecNano(fracStr) { + return parseInt(fracStr.padEnd(9, "0")); + } + + function parseSign(s) { + return s && "+" !== s ? -1 : 1; + } + + function parseInt0(s) { + return void 0 === s ? 0 : parseInt(s); + } + + function padNumber(digits, num) { + return String(num).padStart(digits, "0"); + } + + const padNumber2 = /*@__PURE__*/ bindArgs(padNumber, 2); + + function compareNumbers$1(a, b) { + return Math.sign(a - b); + } + + function divFloorBigInt(num, denom) { + const whole = num / denom; + return num % denom < BigInt(0) ? whole - BigInt(1) : whole; + } + + function divModFloorBigInt(num, divisor) { + const quotient = divFloorBigInt(num, divisor); + return [ quotient, num - quotient * divisor ]; + } + + function divModFloor(num, divisor) { + return [ Math.floor(num / divisor), modFloor(num, divisor) ]; + } + + function modFloor(num, divisor) { + return (num % divisor + divisor) % divisor; + } + + function divTrunc(num, divisor) { + return Math.trunc(num / divisor) || 0; + } + + function hasHalf(num) { + return .5 === Math.abs(num % 1); + } + + function normalizeEraName(era) { + const normalized = era.normalize("NFD").toLowerCase().replace(/[^a-z0-9]/g, ""); + return "bc" === normalized || "b" === normalized ? "bce" : "ad" === normalized || "a" === normalized ? "ce" : normalized; + } + + const isoCalendarImpl = void 0; + + function getCalendarSlotId(calendar) { + return calendar === isoCalendarImpl ? "iso8601" : 0 === calendar ? "gregory" : calendar.id; + } + + function formatMonthCode(monthCodeNumber, isLeapMonth) { + return "M" + padNumber2(monthCodeNumber) + (isLeapMonth ? "L" : ""); + } + + const unitNameMap = { + nanosecond: 0, + microsecond: 1, + millisecond: 2, + second: 3, + minute: 4, + hour: 5, + day: 6, + week: 7, + month: 8, + year: 9 + }; + + const unitNamesAsc = /*@__PURE__*/ Object.keys(unitNameMap); + + const nanoInMicro = nanoInMicro$1; + + const nanoInMilli = nanoInMilli$1; + + const nanoInSec = nanoInSec$1; + + const nanoInMinute = nanoInMinute$1; + + const nanoInHour = nanoInHour$1; + + const nanoInUtcDay = 864e11; + + const bigNanoInMilli = /*@__PURE__*/ BigInt(nanoInMilli); + + const bigNanoInSec = /*@__PURE__*/ BigInt(nanoInSec); + + const bigNanoInUtcDay = /*@__PURE__*/ BigInt(nanoInUtcDay); + + const timeFieldNamesAsc = /*@__PURE__*/ unitNamesAsc.slice(0, 6); + + const timeGetters$1 = /*@__PURE__*/ createPropGetters(timeFieldNamesAsc); + + const calendarDateFieldNamesAsc = [ "day", "month", "year" ]; + + function validateTimeFields(timeFields) { + return constrainTimeFields(timeFields, 1), timeFields; + } + + const maxValues = { + hour: 23, + minute: 59, + second: 59 + }; + + function constrainTimeFields(timeFields, overflow) { + const constrainedFields = {}; + for (const fieldName of timeFieldNamesAsc) { + constrainedFields[fieldName] = clampEntity(fieldName, timeFields[fieldName], 0, maxValues[fieldName] || 999, overflow); + } + return constrainedFields; + } + + function timeFieldsToNano(timeFields) { + return timeFieldsToSec(timeFields) * nanoInSec + timeFieldsToSubsecNano(timeFields); + } + + function timeFieldsToSec(timeFields) { + return 3600 * timeFields.hour + 60 * timeFields.minute + timeFields.second; + } + + function timeFieldsToSubsecNano(timeFields) { + return timeFields.millisecond * nanoInMilli + timeFields.microsecond * nanoInMicro + timeFields.nanosecond; + } + + function nanoToTimeFields(timeNano) { + const [timeMilli, nanoAfterMilli] = divModFloor(timeNano, nanoInMilli); + const [microsecond, nanosecond] = divModFloor(nanoAfterMilli, nanoInMicro); + return milliToTimeFields(timeMilli, microsecond, nanosecond); + } + + function milliToTimeFields(timeMilli, microsecond = 0, nanosecond = 0) { + const [hour, milliAfterHour] = divModFloor(timeMilli, 36e5); + const [minute, milliAfterMinute] = divModFloor(milliAfterHour, 6e4); + const [second, millisecond] = divModFloor(milliAfterMinute, 1e3); + return { + hour: hour, + minute: minute, + second: second, + millisecond: millisecond, + microsecond: microsecond, + nanosecond: nanosecond + }; + } + + function epochNanoToSecMod(epochNano) { + const [epochSec, nano] = divModFloorBigInt(epochNano, bigNanoInSec); + return [ Number(epochSec), Number(nano) ]; + } + + function isoDateTimeToEpochNano(isoDateTime) { + return isoDateToEpochNano(isoDateTime) + BigInt(timeFieldsToNano(isoDateTime)); + } + + function isoDateToEpochNano(isoDate) { + return BigInt(isoDateToEpochDays(isoDate)) * bigNanoInUtcDay; + } + + function isoDateToEpochDays(isoDate) { + return isoArgsToEpochDays(isoDate.year, isoDate.month, isoDate.day); + } + + function isoArgsToEpochDays(isoYear, isoMonth = 1, isoDay = 1) { + const monthIndex = isoMonth - 1; + return isoYear += Math.floor(monthIndex / 12), isoMonth = modFloor(monthIndex, 12), + Date.UTC(isoYear % 400 - 400, isoMonth, 0) / 864e5 + 146097 * (divTrunc(isoYear, 400) + 1) + isoDay; + } + + function epochNanoToIsoDateTime(epochNano) { + const [epochDays, nanoAfterDay] = divModFloorBigInt(epochNano, bigNanoInUtcDay); + return { + ...epochDaysToIsoDate(Number(epochDays)), + ...nanoToTimeFields(Number(nanoAfterDay)) + }; + } + + function epochDaysToIsoDate(epochDays) { + const legacyDate = new Date(864e5 * modFloor(epochDays, 146097)); + return { + year: legacyDate.getUTCFullYear() + 400 * Math.floor(epochDays / 146097), + month: legacyDate.getUTCMonth() + 1, + day: legacyDate.getUTCDate() + }; + } + + function computeIsoMonthCodeParts(month) { + return [ month, 0 ]; + } + + function computeIsoFieldsFromParts(year, month, day) { + return { + year: year, + month: month, + day: day + }; + } + + function computeIsoDaysInMonth(year, month) { + switch (month) { + case 2: + return computeIsoInLeapYear(year) ? 29 : 28; + + case 4: + case 6: + case 9: + case 11: + return 30; + } + return 31; + } + + function computeIsoDaysInYear(year) { + return computeIsoInLeapYear(year) ? 366 : 365; + } + + function computeIsoInLeapYear(year) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + } + + function computeIsoDayOfWeek(isoDateFields) { + return modFloor(isoArgsToEpochDays(isoDateFields.year, isoDateFields.month, isoDateFields.day) + 4, 7) || 7; + } + + function computeIsoDayOfYear(isoDateFields) { + return isoArgsToEpochDays(isoDateFields.year, isoDateFields.month, isoDateFields.day) - isoArgsToEpochDays(isoDateFields.year) + 1; + } + + function computeIsoWeekFields(isoDateFields) { + let yearOfWeek = isoDateFields.year; + let weekOfYear = Math.floor((computeIsoDayOfYear(isoDateFields) - computeIsoDayOfWeek(isoDateFields) + 10) / 7); + let weeksInYear = computeIsoWeeksInYear(yearOfWeek); + return weekOfYear < 1 ? weekOfYear = weeksInYear = computeIsoWeeksInYear(--yearOfWeek) : weekOfYear > weeksInYear && (weekOfYear = 1, + weeksInYear = computeIsoWeeksInYear(++yearOfWeek)), { + weekOfYear: weekOfYear, + yearOfWeek: yearOfWeek, + Ie: weeksInYear + }; + } + + function computeIsoWeeksInYear(year) { + const y0DayOfWeek = computeIsoDayOfWeek({ + year: year, + month: 1, + day: 1 + }); + return 4 === y0DayOfWeek || 3 === y0DayOfWeek && computeIsoInLeapYear(year) ? 53 : 52; + } + + function computeGregoryEraFields({year: year}) { + return year < 1 ? { + era: "bce", + eraYear: 1 - year + } : { + era: "ce", + eraYear: year + }; + } + + function validateIsoDateTimeFields(isoDateTime) { + return validateIsoDateFields(isoDateTime), validateTimeFields(isoDateTime); + } + + function validateIsoDateFields(isoInternals) { + return constrainIsoDateFields(isoInternals, 1), isoInternals; + } + + function constrainIsoDateFields(isoDate, overflow) { + const {year: year} = isoDate; + const month = clampProp(isoDate, "month", 1, 12, overflow); + return { + year: year, + month: month, + day: clampProp(isoDate, "day", 1, computeIsoDaysInMonth(year, month), overflow) + }; + } + + function computeCalendarDateFields(calendar, isoDate) { + return calendar ? calendar.ie(isoDate) : isoDate; + } + + function computeCalendarMonthCodeParts(calendar, year, month) { + return calendar ? calendar.O(year, month) : computeIsoMonthCodeParts(month); + } + + function computeCalendarEraFields(calendar, isoDate) { + return 0 === calendar ? computeGregoryEraFields(isoDate) : calendar ? calendar.h(isoDate) : {}; + } + + function computeCalendarIsoFieldsFromParts(calendar, year, month, day) { + return calendar ? calendar.je(year, month, day) : computeIsoFieldsFromParts(year, month, day); + } + + function computeCalendarMonthsInYearForYear(calendar, year) { + return calendar ? calendar.k(year) : 12; + } + + function computeCalendarDaysInMonthForYearMonth(calendar, year, month) { + return calendar ? calendar.p(year, month) : computeIsoDaysInMonth(year, month); + } + + function computeCalendarMonthCode(calendar, isoDate) { + const {year: year, month: month} = computeCalendarDateFields(calendar, isoDate); + const [monthCodeNumber, isLeapMonth] = computeCalendarMonthCodeParts(calendar, year, month); + return formatMonthCode(monthCodeNumber, isLeapMonth); + } + + function computeCalendarInLeapYear(calendar, isoDate) { + const {year: year} = computeCalendarDateFields(calendar, isoDate); + return calendar ? calendar.u(year) : computeIsoInLeapYear(year); + } + + function computeCalendarMonthsInYear(calendar, isoDate) { + const {year: year} = computeCalendarDateFields(calendar, isoDate); + return computeCalendarMonthsInYearForYear(calendar, year); + } + + function computeCalendarDaysInMonth(calendar, isoDate) { + const {year: year, month: month} = computeCalendarDateFields(calendar, isoDate); + return computeCalendarDaysInMonthForYearMonth(calendar, year, month); + } + + function computeCalendarDaysInYear(calendar, isoDate) { + const {year: year} = computeCalendarDateFields(calendar, isoDate); + return calendar ? calendar.j(year) : computeIsoDaysInYear(year); + } + + function computeCalendarDayOfYear(calendar, isoDate) { + if (!calendar) { + return computeIsoDayOfYear(isoDate); + } + const {year: year} = computeCalendarDateFields(calendar, isoDate); + const yearStartIsoDate = computeCalendarIsoFieldsFromParts(calendar, year, 1, 1); + return isoDateToEpochDays(isoDate) - isoDateToEpochDays(yearStartIsoDate) + 1; + } + + function computeCalendarWeekOfYear(calendar, isoDate) { + return calendar === isoCalendarImpl ? computeIsoWeekFields(isoDate).weekOfYear : void 0; + } + + function computeCalendarYearOfWeek(calendar, isoDate) { + return calendar === isoCalendarImpl ? computeIsoWeekFields(isoDate).yearOfWeek : void 0; + } + + const requireString = /*@__PURE__*/ bindArgs(requireType, "string"); + + function requireType(typeName, arg, entityName = typeName) { + return typeof arg !== typeName && throwTypeError(invalidEntity(entityName, arg)), + arg; + } + + function requireNumberIsInteger(num, entityName = "number") { + return Number.isInteger(num) || throwRangeError(((entityName, num) => `Non-integer ${entityName}: ${num}`)(entityName, num)), + num || 0; + } + + function toString$3(arg) { + return "symbol" == typeof arg && throwTypeError("Cannot convert Symbol to string"), + String(arg); + } + + function toStrictInteger(arg, entityName) { + return requireNumberIsInteger(toFiniteNumber(arg, entityName), entityName); + } + + const epochDisambigMap = { + compatible: 0, + reject: 1, + earlier: 2, + later: 3 + }; + + const roundingModeFuncs = [ Math.floor, num => hasHalf(num) ? Math.floor(num) : Math.round(num), Math.ceil, num => hasHalf(num) ? Math.ceil(num) : Math.round(num), Math.trunc, num => hasHalf(num) ? Math.trunc(num) || 0 : Math.round(num), num => num < 0 ? Math.floor(num) : Math.ceil(num), num => Math.sign(num) * Math.round(Math.abs(num)) || 0, num => hasHalf(num) ? (num = Math.trunc(num) || 0) + num % 2 : Math.round(num) ]; + + function coerceChoiceOption(optionName, enumNameMap, options, defaultChoice = 0) { + const enumArg = options[optionName]; + if (void 0 === enumArg) { + return defaultChoice; + } + const enumStr = toString$3(enumArg); + const enumNum = enumNameMap[enumStr]; + return void 0 === enumNum && throwRangeError(invalidChoice(optionName, enumStr, enumNameMap)), + enumNum; + } + + const coerceEpochDisambig = /*@__PURE__*/ bindArgs(coerceChoiceOption, "disambiguation", epochDisambigMap); + + const epochNanoMax = /*@__PURE__*/ BigInt(1e8) * bigNanoInUtcDay; + + const epochNanoMin = /*@__PURE__*/ BigInt(-1e8) * bigNanoInUtcDay; + + const plainDateEpochNanoMin = epochNanoMin - bigNanoInUtcDay; + + function checkIsoDateTimeInBounds(isoDateTime) { + const epochNano = isoDateToEpochNano(isoDateTime); + return checkIsoDateEpochNanoInBounds(epochNano), epochNano !== plainDateEpochNanoMin || timeFieldsToNano(isoDateTime) || throwRangeError(outOfBoundsDate), + isoDateTime; + } + + function checkIsoDateEpochNanoInBounds(epochNano, allowPlainDateLowerEdge = 1) { + (epochNano < (allowPlainDateLowerEdge ? plainDateEpochNanoMin : epochNanoMin) || epochNano > epochNanoMax) && throwRangeError(outOfBoundsDate); + } + + function checkEpochNanoInBounds(epochNano) { + return (epochNano < epochNanoMin || epochNano > epochNanoMax) && throwRangeError(outOfBoundsDate), + epochNano; + } + + function isoDateTimeAndOffsetToEpochNano(isoDateTime, offsetNano) { + return checkEpochNanoInBounds(isoDateToEpochNano(isoDateTime) + BigInt(timeFieldsToNano(isoDateTime) - offsetNano)); + } + + function createEpochNanoSlots(epochNano) { + return { + epochNanoseconds: epochNano + }; + } + + function createZonedEpochNanoSlots(epochNano, timeZone, calendar) { + return { + calendar: calendar, + timeZone: timeZone, + epochNanoseconds: epochNano + }; + } + + function createDateTimeSlots(isoDateTime, calendar) { + return pluckProps(timeFieldNamesAsc, isoDateTime, createDateSlots(isoDateTime, calendar)); + } + + function createDateSlots(isoDate, calendar) { + return pluckProps(calendarDateFieldNamesAsc, isoDate, { + calendar: calendar + }); + } + + function getEpochMilli(slots) { + return epochNano = slots.epochNanoseconds, Number(divFloorBigInt(epochNano, bigNanoInMilli)); + var epochNano; + } + + function getEpochNano(slots) { + return slots.epochNanoseconds; + } + + function roundToMinute$4(offsetNano) { + return roundNumberToInc(offsetNano, nanoInMinute, 7); + } + + function roundNumberToInc(num, roundingInc, roundingMode) { + return roundWithMode(num / roundingInc, roundingMode) * roundingInc; + } + + function roundWithMode(num, roundingMode) { + return roundingModeFuncs[roundingMode](num); + } + + const zonedEpochSlotsToIso = /*@__PURE__*/ memoize$1(_zonedEpochSlotsToIso, WeakMap); + + function _zonedEpochSlotsToIso(slots) { + const {epochNanoseconds: epochNanoseconds, timeZone: timeZone} = slots; + const offsetNanoseconds = timeZone.C(epochNanoseconds); + return { + ...epochNanoToIsoDateTime(epochNanoseconds + BigInt(offsetNanoseconds)), + offsetNanoseconds: offsetNanoseconds + }; + } + + function getSingleInstantFor(timeZone, isoDateTime, disambig = 0, possibleEpochNanos = timeZone.R(isoDateTime)) { + if (1 === possibleEpochNanos.length) { + return possibleEpochNanos[0]; + } + if (1 === disambig && throwRangeError("Ambiguous offset"), possibleEpochNanos.length) { + return possibleEpochNanos[3 === disambig ? 1 : 0]; + } + const zonedEpochNano = isoDateTimeToEpochNano(isoDateTime); + const gapNano = ((timeZone, zonedEpochNano) => { + const startOffsetNano = timeZone.C(zonedEpochNano - bigNanoInUtcDay); + return (gapNano => (gapNano > nanoInUtcDay && throwRangeError("Out-of-bounds TimeZone gap"), + gapNano))(timeZone.C(zonedEpochNano + bigNanoInUtcDay) - startOffsetNano); + })(timeZone, zonedEpochNano); + const shiftedIsoDateTime = epochNanoToIsoDateTime(zonedEpochNano + BigInt(gapNano * (2 === disambig ? -1 : 1))); + return (possibleEpochNanos = timeZone.R(shiftedIsoDateTime))[2 === disambig ? 0 : possibleEpochNanos.length - 1]; + } + + const offsetRegExp = /*@__PURE__*/ createRegExp("([+-])(\\d{2})(?::?(\\d{2})(?::?(\\d{2})(?:[.,](\\d{1,9}))?)?)?"); + + function parseOffsetNanoMaybe(s, onlyHourMinute) { + const parts = offsetRegExp.exec(s); + if (parts && (s => (s => { + "T" !== s[0] && "t" !== s[0] || (s = s.slice(1)); + const fractionIndex = s.search(/[.,]/); + const main = fractionIndex < 0 ? s : s.slice(0, fractionIndex); + const parts = main.split(":"); + return 1 === parts.length ? /^(?:\d{2}|\d{4}|\d{6})$/i.test(main) : (2 === parts.length || 3 === parts.length) && parts.every(part => 2 === part.length && /^\d{2}$/i.test(part)); + })(s.slice(1)))(parts[0])) { + return ((parts, onlyHourMinute) => { + const firstSubMinutePart = parts[4] || parts[5]; + onlyHourMinute && firstSubMinutePart && throwRangeError(invalidSubstring(firstSubMinutePart)); + const offsetNanoPos = parseInt0(parts[2]) * nanoInHour + parseInt0(parts[3]) * nanoInMinute + parseInt0(parts[4]) * nanoInSec + parseSubsecNano(parts[5] || ""); + return offsetNano = offsetNanoPos * parseSign(parts[1]), Math.abs(offsetNano) >= nanoInUtcDay && throwRangeError("Out-of-bounds offset"), + offsetNano; + var offsetNano; + })(parts, onlyHourMinute); + } + } + + const RawDateTimeFormat = Intl.DateTimeFormat; + + function formatEpochMilliToPartsRecord(intlFormat, epochMilli) { + epochMilli < -864e13 && throwRangeError(outOfBoundsDate); + const parts = intlFormat.formatToParts(epochMilli); + const hash = {}; + for (const part of parts) { + hash[part.type] = part.value; + } + return hash; + } + + const timeZonePeriodDaysByName = { + "El_Aaiun": 17, + "Tucuman": 12, + "Tirane": 11, + "Riga": 10, + "Simferopol": 9, + "Vienna": 9, + "Tunis": 8, + "Boa_Vista": 6, + "Fortaleza": 6, + "Maceio": 6, + "Noronha": 6, + "Recife": 6, + "Gaza": 6, + "Hebron": 6, + "DeNoronha": 6 + }; + + const minPossibleTransitionSec = -388152e4; + + function formatInstantIsoAuto(instantSlots) { + return formatIsoDateTimeFields(epochNanoToIsoDateTime(instantSlots.epochNanoseconds), void 0) + "Z"; + } + + function formatZonedDateTimeIsoAuto(zonedDateTimeSlots) { + const calendar = zonedDateTimeSlots.calendar; + const timeZone = zonedDateTimeSlots.timeZone; + const offsetNano = timeZone.C(zonedDateTimeSlots.epochNanoseconds); + return formatIsoDateTimeFields(epochNanoToIsoDateTime(zonedDateTimeSlots.epochNanoseconds + BigInt(offsetNano)), void 0) + formatOffsetNano(roundToMinute$4(offsetNano)) + formatTimeZone(timeZone.id, 0) + (calendar === isoCalendarImpl ? "" : formatCalendarId(getCalendarSlotId(calendar), 0)); + } + + function formatDateTimeIsoAuto(isoDateTimeSlots) { + const calendar = isoDateTimeSlots.calendar; + return formatIsoDateTimeFields(isoDateTimeSlots, void 0) + (calendar === isoCalendarImpl ? "" : formatCalendarId(getCalendarSlotId(calendar), 0)); + } + + function formatIsoDateTimeFields(isoDateTime, subsecDigits) { + return formatIsoDateFields(isoDateTime) + "T" + formatTimeFields(isoDateTime, subsecDigits); + } + + function formatIsoDateFields(isoDateFields) { + return formatIsoYearMonthFields(isoDateFields) + "-" + padNumber2(isoDateFields.day); + } + + function formatIsoYearMonthFields(isoDateFields) { + const {year: year} = isoDateFields; + return (year < 0 || year > 9999 ? getSignStr(year) + padNumber(6, Math.abs(year)) : padNumber(4, year)) + "-" + padNumber2(isoDateFields.month); + } + + function formatTimeFields(timeFields, subsecDigits) { + const parts = [ padNumber2(timeFields.hour), padNumber2(timeFields.minute) ]; + return -1 !== subsecDigits && parts.push(padNumber2(timeFields.second) + ((millisecond, microsecond, nanosecond, subsecDigits) => formatSubsecNano(millisecond * nanoInMilli + microsecond * nanoInMicro + nanosecond, subsecDigits))(timeFields.millisecond, timeFields.microsecond, timeFields.nanosecond, subsecDigits)), + parts.join(":"); + } + + function formatOffsetNano(offsetNano, offsetDisplay = 0) { + if (1 === offsetDisplay) { + return ""; + } + const [hour, nanoRemainder0] = divModFloor(Math.abs(offsetNano), nanoInHour); + const [minute, nanoRemainder1] = divModFloor(nanoRemainder0, nanoInMinute); + const [second, nanoRemainder2] = divModFloor(nanoRemainder1, nanoInSec); + return getSignStr(offsetNano) + padNumber2(hour) + ":" + padNumber2(minute) + (second || nanoRemainder2 ? ":" + padNumber2(second) + formatSubsecNano(nanoRemainder2) : ""); + } + + function formatTimeZone(timeZoneId, timeZoneDisplay) { + return 1 !== timeZoneDisplay ? "[" + (2 === timeZoneDisplay ? "!" : "") + timeZoneId + "]" : ""; + } + + function formatCalendarId(calendarId, isCritical) { + return "[" + (isCritical ? "!" : "") + "u-ca=" + calendarId + "]"; + } + + const trailingZerosRE = /0+$/; + + function formatSubsecNano(totalNano, subsecDigits) { + let s = padNumber(9, totalNano); + return s = void 0 === subsecDigits ? s.replace(trailingZerosRE, "") : s.slice(0, subsecDigits), + s ? "." + s : ""; + } + + function getSignStr(num) { + return num < 0 ? "-" : "+"; + } + + const icuRegExp = /^(AC|AE|AG|AR|AS|BE|BS|CA|CN|CS|CT|EA|EC|IE|IS|JS|MI|NE|NS|PL|PN|PR|PS|SS|VS)T$/; + + const badCharactersRegExp = /[^\w\/:+-]+/; + + function refineTimeZoneId(rawId) { + return resolveTimeZoneId(requireString(rawId)); + } + + function resolveTimeZoneId(rawId) { + return resolveTimeZoneRecord(rawId).id; + } + + function resolveTimeZoneRecord(rawId) { + const upperRawId = rawId.toUpperCase(); + const offsetRecord = (upperRawId => { + const offsetNano = parseOffsetNanoMaybe(upperRawId, 1); + if (void 0 !== offsetNano) { + return { + id: formatOffsetNano(offsetNano), + _: offsetNano, + o: offsetNano + }; + } + })(upperRawId); + if (offsetRecord) { + return { + kind: "fixed", + ...offsetRecord + }; + } + const normId = "UTC" === upperRawId ? "UTC" : (rawId => (badCharactersRegExp.test(rawId) && throwRangeError(invalidTimeZone(rawId)), + icuRegExp.test(rawId) && throwRangeError("Forbidden ICU TimeZone"), rawId.toLowerCase().split("/").map((part, partI) => (part.length <= 3 || /\d/.test(part)) && !/etc|yap/.test(part) ? part.toUpperCase() : part.replace(/baja|dumont|[a-z]+/g, (a, i) => a.length <= 2 && !partI || "in" === a || "chat" === a ? a.toUpperCase() : a.length > 2 || !i ? capitalize(a).replace(/island|noronha|murdo|rivadavia|urville/, capitalize) : a)).join("/")))(rawId); + return queryNamedTimeZoneRecord(normId); + } + + const queryNamedTimeZoneRecord = /*@__PURE__*/ memoize$1(normId => { + if ("UTC" === normId) { + return { + kind: "utc", + id: normId, + o: normId + }; + } + const upperNormId = normId.toUpperCase(); + const format = queryTimeZoneIntlFormat(upperNormId); + return { + kind: "named", + id: normId, + format: format, + o: format.resolvedOptions().timeZone + }; + }); + + const queryTimeZoneIntlFormat = /*@__PURE__*/ memoize$1(upperNormId => new RawDateTimeFormat("en-u-hc-h23", { + calendar: "iso8601", + timeZone: upperNormId, + era: "short", + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric" + })); + + function queryTimeZone(rawTimeZoneId) { + const record = resolveTimeZoneRecord(rawTimeZoneId); + return queryTimeZoneRecord(record.id, record); + } + + const queryTimeZoneRecord = /*@__PURE__*/ memoize$1((normTimeZoneId, record) => "named" === record.kind ? new IntlTimeZone(normTimeZoneId, record.o, record.format) : new FixedTimeZone(normTimeZoneId, record.o, "fixed" === record.kind ? record._ : 0)); + + class FixedTimeZone { + constructor(id, compareKey, offsetNano) { + this.id = id, this.o = compareKey, this._ = offsetNano; + } + C() { + return this._; + } + R(isoDateTime) { + return [ isoDateTimeAndOffsetToEpochNano(isoDateTime, this._) ]; + } + U() {} + } + + class IntlTimeZone { + constructor(id, compareKey, format) { + this.id = id, this.o = compareKey, this.qe = ((computeOffsetSec, periodDays) => { + const getSample = memoize$1(computeOffsetSec); + const getSplit = memoize$1(createSplitTuple); + const periodSec = 86400 * periodDays; + function getOffsetSec(epochSec) { + const [startEpochSec, endEpochSec] = computePeriod(epochSec, periodSec); + const clampedStartEpochSec = clampIntlSampleEpochSec(startEpochSec); + const clampedEndEpochSec = clampIntlSampleEpochSec(endEpochSec); + const startOffsetSec = getSample(clampedStartEpochSec); + const endOffsetSec = getSample(clampedEndEpochSec); + return startOffsetSec === endOffsetSec ? startOffsetSec : pinch(getSplit(clampedStartEpochSec, clampedEndEpochSec), startOffsetSec, endOffsetSec, epochSec); + } + function pinch(split, startOffsetSec, endOffsetSec, forEpochSec) { + let offsetSec; + let splitDurSec; + for (;(void 0 === forEpochSec || void 0 === (offsetSec = forEpochSec < split[0] ? startOffsetSec : forEpochSec >= split[1] ? endOffsetSec : void 0)) && (splitDurSec = split[1] - split[0]); ) { + const middleEpochSec = split[0] + Math.floor(splitDurSec / 2); + computeOffsetSec(middleEpochSec) === endOffsetSec ? split[1] = middleEpochSec : split[0] = middleEpochSec + 1; + } + return offsetSec; + } + return { + Ee(zonedEpochSec) { + const wideOffsetSec0 = getOffsetSec(zonedEpochSec - 86400); + const wideOffsetSec1 = getOffsetSec(zonedEpochSec + 86400); + const wideUtcEpochSec0 = zonedEpochSec - wideOffsetSec0; + const wideUtcEpochSec1 = zonedEpochSec - wideOffsetSec1; + if (wideOffsetSec0 === wideOffsetSec1) { + return [ wideUtcEpochSec0 ]; + } + const narrowOffsetSec0 = getOffsetSec(wideUtcEpochSec0); + return narrowOffsetSec0 === getOffsetSec(wideUtcEpochSec1) ? [ zonedEpochSec - narrowOffsetSec0 ] : wideOffsetSec0 > wideOffsetSec1 ? [ wideUtcEpochSec0, wideUtcEpochSec1 ] : []; + }, + De: getOffsetSec, + U: function getTransition(epochSec, direction) { + if (direction > 0 && epochSec >= 864e10) { + return; + } + if (direction < 0) { + if (epochSec <= minPossibleTransitionSec) { + return; + } + const lookaheadEpochSec = getCurrentEpochSec() + 94867200; + if (epochSec > lookaheadEpochSec) { + return getTransition(lookaheadEpochSec, -1); + } + } + const searchEpochSec = direction > 0 ? Math.max(epochSec, minPossibleTransitionSec) : epochSec; + let [startEpochSec, endEpochSec] = computePeriod(searchEpochSec, periodSec); + const inc = periodSec * direction; + const searchLimit = direction > 0 ? Math.max(epochSec, getCurrentEpochSec()) + 94867200 : minPossibleTransitionSec; + const inBounds = () => direction < 0 ? endEpochSec > searchLimit : startEpochSec < searchLimit; + for (;inBounds(); ) { + const clampedStartEpochSec = clampIntlSampleEpochSec(startEpochSec); + const clampedEndEpochSec = clampIntlSampleEpochSec(endEpochSec); + const startOffsetSec = getSample(clampedStartEpochSec); + const endOffsetSec = getSample(clampedEndEpochSec); + if (startOffsetSec !== endOffsetSec) { + const split = getSplit(clampedStartEpochSec, clampedEndEpochSec); + pinch(split, startOffsetSec, endOffsetSec); + const transitionEpochSec = split[0]; + if ((compareNumbers$1(transitionEpochSec, epochSec) || 1) === direction) { + return transitionEpochSec; + } + } + startEpochSec += inc, endEpochSec += inc; + } + } + }; + })((format => epochSec => { + const intlParts = formatEpochMilliToPartsRecord(format, 1e3 * epochSec); + return 86400 * isoArgsToEpochDays((intlParts => { + const relatedYear = intlParts.relatedYear; + if (void 0 !== relatedYear) { + return parseInt(relatedYear); + } + const year = parseInt(intlParts.year); + return void 0 !== intlParts.era && "bce" === normalizeEraName(intlParts.era) ? 1 - year : year; + })(intlParts), parseInt(intlParts.month), parseInt(intlParts.day)) + 3600 * parseInt(intlParts.hour) + 60 * parseInt(intlParts.minute) + parseInt(intlParts.second) - epochSec; + })(format), (timeZoneId => { + const timeZoneName = timeZoneId.split("/").pop(); + return timeZonePeriodDaysByName[timeZoneName] || 60; + })(id)); + } + C(epochNano) { + return this.qe.De((epochNano => epochNanoToSecMod(epochNano)[0])(epochNano)) * nanoInSec; + } + R(isoDateTime) { + const zonedEpochSec = 86400 * isoDateToEpochDays(isoDateTime) + timeFieldsToSec(isoDateTime); + const subsecNano = timeFieldsToSubsecNano(isoDateTime); + return this.qe.Ee(zonedEpochSec).map(epochSec => checkEpochNanoInBounds(BigInt(epochSec) * bigNanoInSec + BigInt(subsecNano))); + } + U(epochNano, direction) { + const [epochSec, subsecNano] = epochNanoToSecMod(epochNano); + const resEpochSec = this.qe.U(epochSec + (direction > 0 || subsecNano ? 1 : 0), direction); + if (void 0 !== resEpochSec) { + return BigInt(resEpochSec) * bigNanoInSec; + } + } + } + + function getCurrentEpochSec() { + return Math.floor(Date.now() / 1e3); + } + + function createSplitTuple(startEpochSec, endEpochSec) { + return [ startEpochSec, endEpochSec ]; + } + + function computePeriod(epochSec, periodSec) { + const startEpochSec = Math.floor(epochSec / periodSec) * periodSec; + return [ startEpochSec, startEpochSec + periodSec ]; + } + + function clampIntlSampleEpochSec(epochSec) { + return constrainToRange(epochSec, -1e10, 864e10); + } + + function instantToZonedDateTime(instantSlots, timeZone, calendar) { + return createZonedEpochNanoSlots(instantSlots.epochNanoseconds, timeZone, calendar); + } + + function plainDateTimeToZonedDateTime(plainDateTimeSlots, timeZone, options) { + const epochNano = ((timeZone, isoDateTime, options) => { + const epochDisambig = (options => coerceEpochDisambig(normalizeOptions(options)))(options); + return getSingleInstantFor(timeZone, isoDateTime, epochDisambig); + })(timeZone, plainDateTimeSlots, options); + return createZonedEpochNanoSlots(checkEpochNanoInBounds(epochNano), timeZone, plainDateTimeSlots.calendar); + } + + function epochMilliToInstant(epochMilli) { + return createEpochNanoSlots(checkEpochNanoInBounds(BigInt(toStrictInteger(epochMilli)) * bigNanoInMilli)); + } + + const PlainDateTimeBranding = "PlainDateTime"; + + const ZonedDateTimeBranding = "ZonedDateTime"; + + const InstantBranding = "Instant"; + + function defineTemporalClass(branding, cls, getSlots, ...getterMaps) { + Object.defineProperties(cls, createNameDescriptors(branding)), Object.defineProperties(cls.prototype, createStringTagDescriptors("Temporal." + branding)); + for (const getterMap of getterMaps) { + defineSlotGetters(cls.prototype, getSlots, getterMap); + } + return cls; + } + + function defineSlotGetters(destPrototype, getSlots, getterMap) { + Object.defineProperties(destPrototype, mapProps(getter => ({ + get() { + return getter(getSlots(this)); + }, + configurable: 1 + }), getterMap)); + } + + const attachDebugString = "noop" === noop.name ? instance => { + Object.defineProperty(instance, "_str_", { + value: instance.toJSON() + }); + } : noop; + + function invalidRecordType() { + throwTypeError(invalidCallingContext); + } + + function forbiddenValueOf() { + throwTypeError(forbiddenValueOf$1); + } + + const dateFieldGetters$1 = { + era(slots) { + return computeCalendarEraFields(slots.calendar, slots).era; + }, + eraYear(slots) { + return computeCalendarEraFields(slots.calendar, slots).eraYear; + }, + year(slots) { + return computeCalendarDateFields(slots.calendar, slots).year; + }, + month(slots) { + return computeCalendarDateFields(slots.calendar, slots).month; + }, + monthCode(slots) { + return computeCalendarMonthCode(slots.calendar, slots); + }, + day(slots) { + return computeCalendarDateFields(slots.calendar, slots).day; + } + }; + + const yearMonthDerivedGetters = { + daysInMonth(slots) { + return computeCalendarDaysInMonth(slots.calendar, slots); + }, + daysInYear(slots) { + return computeCalendarDaysInYear(slots.calendar, slots); + }, + monthsInYear(slots) { + return computeCalendarMonthsInYear(slots.calendar, slots); + }, + inLeapYear(slots) { + return computeCalendarInLeapYear(slots.calendar, slots); + } + }; + + const dateDerivedGetters = { + dayOfWeek(slots) { + return computeIsoDayOfWeek(slots); + }, + dayOfYear(slots) { + return computeCalendarDayOfYear(slots.calendar, slots); + }, + weekOfYear(slots) { + return computeCalendarWeekOfYear(slots.calendar, slots); + }, + yearOfWeek(slots) { + return computeCalendarYearOfWeek(slots.calendar, slots); + }, + daysInWeek() { + return 7; + }, + daysInMonth(slots) { + return computeCalendarDaysInMonth(slots.calendar, slots); + }, + daysInYear(slots) { + return computeCalendarDaysInYear(slots.calendar, slots); + }, + monthsInYear(slots) { + return computeCalendarMonthsInYear(slots.calendar, slots); + }, + inLeapYear(slots) { + return computeCalendarInLeapYear(slots.calendar, slots); + } + }; + + function createNativeGetters(shimGetters) { + return createPropGetters(Object.keys(shimGetters)); + } + + const timeGetters = /*@__PURE__*/ createNativeGetters(timeGetters$1); + + const dateFieldGetters = /*@__PURE__*/ createNativeGetters(dateFieldGetters$1); + + createNativeGetters(yearMonthDerivedGetters), createNativeGetters(dateDerivedGetters); + + const PlainDateTimeRecordBranding = `${PlainDateTimeBranding}Record`; + + const ZonedDateTimeRecordBranding = `${ZonedDateTimeBranding}Record`; + + const InstantRecordBranding = `${InstantBranding}Record`; + + const calendarMap = /*@__PURE__*/ new WeakMap; + + const instantMap = /*@__PURE__*/ new WeakMap; + + const zonedDateTimeMap = /*@__PURE__*/ new WeakMap; + + const plainDateTimeMap = /*@__PURE__*/ new WeakMap; + + function getCalendarSlots(record) { + return getCalendarSlotsIfPresent(record) || invalidRecordType(); + } + + function getCalendarSlotsIfPresent(record) { + return calendarMap.get(record); + } + + function getInstantSlots(record) { + return getInstantSlotsIfPresent(record) || invalidRecordType(); + } + + function getInstantSlotsIfPresent(record) { + return instantMap.get(record); + } + + function setInstantSlots(instance, slots) { + instantMap.set(instance, slots); + } + + function getZonedDateTimeSlots(record) { + return getZonedDateTimeSlotsIfPresent(record) || invalidRecordType(); + } + + function getZonedDateTimeSlotsIfPresent(record) { + return zonedDateTimeMap.get(record); + } + + function setZonedDateTimeSlots(instance, slots) { + zonedDateTimeMap.set(instance, slots); + } + + function getPlainDateTimeSlots(record) { + return getPlainDateTimeSlotsIfPresent(record) || invalidRecordType(); + } + + function getPlainDateTimeSlotsIfPresent(record) { + return plainDateTimeMap.get(record); + } + + function setPlainDateTimeSlots(instance, slots) { + plainDateTimeMap.set(instance, slots); + } + + function getCalendarRecordId(record) { + return getCalendarSlots(record).id; + } + + function getCalendarRecordImplCreator(record) { + const getImpl = getCalendarSlots(record).Be; + return getImpl || throwRangeError(exoticCalendarRequired(getCalendarRecordId(record), "getExotic or getAny")), + getImpl; + } + + function refineNativeCalendarArgMaybe(calendarRecord) { + if (void 0 !== calendarRecord) { + return getValidatedCalendarId(calendarRecord); + } + } + + function getValidatedCalendarId(record) { + return getCalendarRecordImplCreator(record), getCalendarRecordId(record); + } + + const getNativePlainDateTime = getPlainDateTimeSlots; + + const NativePlainDateTimeRecord = /*@__PURE__*/ defineTemporalClass(PlainDateTimeRecordBranding, class { + get calendarId() { + return getNativePlainDateTime(this).calendarId; + } + toJSON() { + return getNativePlainDateTime(this).toJSON(); + } + valueOf() { + return getNativePlainDateTime(this).valueOf(); + } + }, getNativePlainDateTime, dateFieldGetters, timeGetters); + + function createNativePlainDateTimeRecord(native) { + const instance = Object.create(NativePlainDateTimeRecord.prototype); + return setPlainDateTimeSlots(instance, native), attachDebugString(instance), instance; + } + + function create$5$1(isoYear, isoMonth, isoDay, hour, minute, second, millisecond, microsecond, nanosecond, calendar) { + return createNativePlainDateTimeRecord(new NativeTemporal.PlainDateTime(isoYear, isoMonth, isoDay, hour, minute, second, millisecond, microsecond, nanosecond, refineNativeCalendarArgMaybe(calendar))); + } + + function toZonedDateTime$1$1(record, timeZoneId, options) { + return createNativeZonedDateTimeRecord(getNativePlainDateTime(record).toZonedDateTime(timeZoneId, options)); + } + + const getNativeZonedDateTime = getZonedDateTimeSlots; + + const NativeZonedDateTimeRecord = /*@__PURE__*/ defineTemporalClass(ZonedDateTimeRecordBranding, class { + get calendarId() { + return getNativeZonedDateTime(this).calendarId; + } + get timeZoneId() { + return getNativeZonedDateTime(this).timeZoneId; + } + get epochMilliseconds() { + return getNativeZonedDateTime(this).epochMilliseconds; + } + get epochNanoseconds() { + return getNativeZonedDateTime(this).epochNanoseconds; + } + toJSON() { + return getNativeZonedDateTime(this).toJSON(); + } + valueOf() { + return getNativeZonedDateTime(this).valueOf(); + } + }, getNativeZonedDateTime, dateFieldGetters, timeGetters); + + function createNativeZonedDateTimeRecord(native) { + const instance = Object.create(NativeZonedDateTimeRecord.prototype); + return setZonedDateTimeSlots(instance, native), attachDebugString(instance), instance; + } + + function offsetNanoseconds$2(record) { + return getNativeZonedDateTime(record).offsetNanoseconds; + } + + const getNativeInstant = getInstantSlots; + + const NativeInstantRecord = /*@__PURE__*/ defineTemporalClass(InstantRecordBranding, class { + get epochMilliseconds() { + return getNativeInstant(this).epochMilliseconds; + } + get epochNanoseconds() { + return getNativeInstant(this).epochNanoseconds; + } + toJSON() { + return getNativeInstant(this).toJSON(); + } + valueOf() { + return getNativeInstant(this).valueOf(); + } + }); + + function createNativeInstantRecord(native) { + const instance = Object.create(NativeInstantRecord.prototype); + return setInstantSlots(instance, native), attachDebugString(instance), instance; + } + + function fromEpochMilliseconds$2(epochMilliseconds) { + return createNativeInstantRecord(NativeTemporal.Instant.fromEpochMilliseconds(epochMilliseconds)); + } + + function toZonedDateTimeISO$2(record, timeZoneId) { + return createNativeZonedDateTimeRecord(getNativeInstant(record).toZonedDateTimeISO(timeZoneId)); + } + + function refineShimCalendarArgMaybe(calendarRecord) { + return void 0 === calendarRecord ? isoCalendarImpl : getCalendarRecordImpl(calendarRecord); + } + + function getCalendarRecordImpl(record) { + return getCalendarRecordImplCreator(record)(); + } + + const getShimPlainDateTimeSlots = getPlainDateTimeSlots; + + const ShimPlainDateTimeRecord = /*@__PURE__*/ defineTemporalClass(PlainDateTimeRecordBranding, class { + get calendarId() { + return getCalendarSlotId(getShimPlainDateTimeSlots(this).calendar); + } + toJSON() { + return formatDateTimeIsoAuto(getShimPlainDateTimeSlots(this)); + } + valueOf() { + return forbiddenValueOf(); + } + }, getShimPlainDateTimeSlots, dateFieldGetters$1, timeGetters$1); + + function createShimPlainDateTimeRecord(slots) { + const instance = Object.create(ShimPlainDateTimeRecord.prototype); + return setPlainDateTimeSlots(instance, slots), attachDebugString(instance), instance; + } + + function create$5(isoYear, isoMonth, isoDay, hour = 0, minute = 0, second = 0, millisecond = 0, microsecond = 0, nanosecond = 0, calendar) { + const fields = checkIsoDateTimeInBounds(validateIsoDateTimeFields(mapProps(toIntegerWithTrunc, { + year: isoYear, + month: isoMonth, + day: isoDay, + hour: hour, + minute: minute, + second: second, + millisecond: millisecond, + microsecond: microsecond, + nanosecond: nanosecond + }))); + const calendarImpl = refineShimCalendarArgMaybe(calendar); + return createShimPlainDateTimeRecord(createDateTimeSlots(fields, calendarImpl)); + } + + function toZonedDateTime$1(record, timeZoneId, options) { + return createShimZonedDateTimeRecord(plainDateTimeToZonedDateTime(getShimPlainDateTimeSlots(record), queryTimeZone(refineTimeZoneId(timeZoneId)), options)); + } + + const getShimZonedDateTimeSlots = getZonedDateTimeSlots; + + const ShimZonedDateTimeRecord = /*@__PURE__*/ defineTemporalClass(ZonedDateTimeRecordBranding, class { + get calendarId() { + return getCalendarSlotId(getShimZonedDateTimeSlots(this).calendar); + } + get timeZoneId() { + return getShimZonedDateTimeSlots(this).timeZone.id; + } + get epochMilliseconds() { + return getEpochMilli(getShimZonedDateTimeSlots(this)); + } + get epochNanoseconds() { + return getEpochNano(getShimZonedDateTimeSlots(this)); + } + toJSON() { + return formatZonedDateTimeIsoAuto(getShimZonedDateTimeSlots(this)); + } + valueOf() { + return forbiddenValueOf(); + } + }, getShimZonedDateTimeIsoSlots, dateFieldGetters$1, timeGetters$1); + + function createShimZonedDateTimeRecord(slots) { + const instance = Object.create(ShimZonedDateTimeRecord.prototype); + return setZonedDateTimeSlots(instance, slots), attachDebugString(instance), instance; + } + + function getShimZonedDateTimeIsoSlots(record) { + const slots = getShimZonedDateTimeSlots(record); + return { + ...zonedEpochSlotsToIso(slots), + calendar: slots.calendar + }; + } + + function offsetNanoseconds$1(record) { + return zonedEpochSlotsToIso(getShimZonedDateTimeSlots(record)).offsetNanoseconds; + } + + const getShimInstantSlots = getInstantSlots; + + const ShimInstantRecord = /*@__PURE__*/ defineTemporalClass(InstantRecordBranding, class { + get epochMilliseconds() { + return getEpochMilli(getShimInstantSlots(this)); + } + get epochNanoseconds() { + return getEpochNano(getShimInstantSlots(this)); + } + toJSON() { + return formatInstantIsoAuto(getShimInstantSlots(this)); + } + valueOf() { + return forbiddenValueOf(); + } + }); + + function createShimInstantRecord(slots) { + const instance = Object.create(ShimInstantRecord.prototype); + return setInstantSlots(instance, slots), attachDebugString(instance), instance; + } + + function fromEpochMilliseconds$1(epochMilliseconds) { + return createShimInstantRecord(epochMilliToInstant(epochMilliseconds)); + } + + function toZonedDateTimeISO$1(record, timeZoneId) { + return createShimZonedDateTimeRecord(instantToZonedDateTime(getShimInstantSlots(record), queryTimeZone(refineTimeZoneId(timeZoneId)))); + } + + const offsetNanoseconds = NativeTemporal ? offsetNanoseconds$2 : offsetNanoseconds$1; + + const create = NativeTemporal ? create$5$1 : create$5; + + const toZonedDateTime = NativeTemporal ? toZonedDateTime$1$1 : toZonedDateTime$1; + + const fromEpochMilliseconds = NativeTemporal ? fromEpochMilliseconds$2 : fromEpochMilliseconds$1; + + const toZonedDateTimeISO = NativeTemporal ? toZonedDateTimeISO$2 : toZonedDateTimeISO$1; + + // Adding + function addWeeks(m, n) { + let a = dateToUtcArray(m); + a[2] += n * 7; + return arrayToUtcDate(a); + } + function addDays(m, n) { + let a = dateToUtcArray(m); + a[2] += n; + return arrayToUtcDate(a); + } + function addMs(m, n) { + let a = dateToUtcArray(m); + a[6] += n; + return arrayToUtcDate(a); + } + // Diffing (all return floats) + // TODO: why not use ranges? + function diffWeeks(m0, m1) { + return diffDays(m0, m1) / 7; + } + function diffDays(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24); + } + function diffHours(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60); + } + function diffMinutes(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60); + } + function diffSeconds(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / 1000; + } + function diffDayAndTime(m0, m1) { + let m0day = startOfDay(m0); + let m1day = startOfDay(m1); + return { + years: 0, + months: 0, + days: Math.round(diffDays(m0day, m1day)), + milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()), + }; + } + // Diffing Whole Units + function diffWholeWeeks(m0, m1) { + let d = diffWholeDays(m0, m1); + if (d !== null && d % 7 === 0) { + return d / 7; + } + return null; + } + function diffWholeDays(m0, m1) { + if (timeAsMs(m0) === timeAsMs(m1)) { + return Math.round(diffDays(m0, m1)); + } + return null; + } + // Start-Of + function startOfDay(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + ]); + } + function startOfHour(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + ]); + } + function startOfMinute(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + m.getUTCMinutes(), + ]); + } + function startOfSecond(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + m.getUTCMinutes(), + m.getUTCSeconds(), + ]); + } + // Week Computation + function weekOfYear(marker, dow, doy) { + let y = marker.getUTCFullYear(); + let w = weekOfGivenYear(marker, y, dow, doy); + if (w < 1) { + return weekOfGivenYear(marker, y - 1, dow, doy); + } + let nextW = weekOfGivenYear(marker, y + 1, dow, doy); + if (nextW >= 1) { + return Math.min(w, nextW); + } + return w; + } + function weekOfGivenYear(marker, year, dow, doy) { + let firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]); + let dayStart = startOfDay(marker); + let days = Math.round(diffDays(firstWeekStart, dayStart)); + return Math.floor(days / 7) + 1; // zero-indexed + } + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + // first-week day -- which january is always in the first week (4 for iso, 1 for other) + let fwd = 7 + dow - doy; + // first-week day local weekday -- which local weekday is fwd + let fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7; + return -fwdlw + fwd - 1; + } + // Array Conversion + function dateToLocalArray(date) { + return [ + date.getFullYear(), + date.getMonth(), + date.getDate(), + date.getHours(), + date.getMinutes(), + date.getSeconds(), + date.getMilliseconds(), + ]; + } + function arrayToLocalDate(a) { + return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month + a[3] || 0, a[4] || 0, a[5] || 0); + } + function dateToUtcArray(date) { + return [ + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds(), + ]; + } + function arrayToUtcDate(a) { + // according to web standards (and Safari), a month index is required. + // massage if only given a year. + if (a.length === 1) { + a = a.concat([0]); + } + return new Date(Date.UTC(...a)); + } + // Other Utils + function isValidDate(m) { + return !isNaN(m.valueOf()); + } + function timeAsMs(m) { + return m.getUTCHours() * 1000 * 60 * 60 + + m.getUTCMinutes() * 1000 * 60 + + m.getUTCSeconds() * 1000 + + m.getUTCMilliseconds(); + } + + let calendarSystemClassMap = {}; + function registerCalendarSystem(name, theClass) { + calendarSystemClassMap[name] = theClass; + } + function createCalendarSystem(name) { + return new calendarSystemClassMap[name](); + } + class GregorianCalendarSystem { + getMarkerYear(d) { + return d.getUTCFullYear(); + } + getMarkerMonth(d) { + return d.getUTCMonth(); + } + getMarkerDay(d) { + return d.getUTCDate(); + } + arrayToMarker(arr) { + return arrayToUtcDate(arr); + } + markerToArray(marker) { + return dateToUtcArray(marker); + } + } + registerCalendarSystem('gregory', GregorianCalendarSystem); + + function parseRange(input, dateEnv) { + let start = null; + let end = null; + if (input.start) { + start = dateEnv.createMarker(input.start); + } + if (input.end) { + end = dateEnv.createMarker(input.end); + } + if (!start && !end) { + return null; + } + if (start && end && end < start) { + return null; + } + return { start, end }; + } + // SIDE-EFFECT: will mutate ranges. + // Will return a new array result. + function invertRanges(ranges, constraintRange) { + let invertedRanges = []; + let { start } = constraintRange; // the end of the previous range. the start of the new range + let i; + let dateRange; + // ranges need to be in order. required for our date-walking algorithm + ranges.sort(compareRanges); + for (i = 0; i < ranges.length; i += 1) { + dateRange = ranges[i]; + // add the span of time before the event (if there is any) + if (dateRange.start > start) { // compare millisecond time (skip any ambig logic) + invertedRanges.push({ start, end: dateRange.start }); + } + if (dateRange.end > start) { + start = dateRange.end; + } + } + // add the span of time after the last event (if there is any) + if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic) + invertedRanges.push({ start, end: constraintRange.end }); + } + return invertedRanges; + } + function compareRanges(range0, range1) { + return range0.start.valueOf() - range1.start.valueOf(); // earlier ranges go first + } + function intersectRanges(range0, range1) { + let { start, end } = range0; + let newRange = null; + if (range1.start !== null) { + if (start === null) { + start = range1.start; + } + else { + start = new Date(Math.max(start.valueOf(), range1.start.valueOf())); + } + } + if (range1.end != null) { + if (end === null) { + end = range1.end; + } + else { + end = new Date(Math.min(end.valueOf(), range1.end.valueOf())); + } + } + if (start === null || end === null || start < end) { + newRange = { start, end }; + } + return newRange; + } + function rangesEqual(range0, range1) { + return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) && + (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf()); + } + function rangesIntersect(range0, range1) { + return (range0.end === null || range1.start === null || range0.end > range1.start) && + (range0.start === null || range1.end === null || range0.start < range1.end); + } + function rangeContainsRange(outerRange, innerRange) { + return (outerRange.start === null || (innerRange.start !== null && innerRange.start >= outerRange.start)) && + (outerRange.end === null || (innerRange.end !== null && innerRange.end <= outerRange.end)); + } + function rangeContainsMarker(range, date) { + return (range.start === null || date >= range.start) && + (range.end === null || date < range.end); + } + // If the given date is not within the given range, move it inside. + // (If it's past the end, make it one millisecond before the end). + function constrainMarkerToRange(date, range) { + if (range.start != null && date < range.start) { + return range.start; + } + if (range.end != null && date >= range.end) { + return new Date(range.end.valueOf() - 1); + } + return date; + } + + function expandZonedMarker(dateInfo, calendarSystem) { + let a = calendarSystem.markerToArray(dateInfo.marker); + return { + marker: dateInfo.marker, + timeZoneOffset: dateInfo.timeZoneOffset, + array: a, + year: a[0], + month: a[1], + day: a[2], + hour: a[3], + minute: a[4], + second: a[5], + millisecond: a[6], + }; + } + + function createVerboseFormattingArg(start, end, context) { + let startInfo = expandZonedMarker(start, context.calendarSystem); + let endInfo = end ? expandZonedMarker(end, context.calendarSystem) : null; + return { + date: startInfo, + start: startInfo, + end: endInfo, + timeZone: context.timeZone, + localeCodes: context.locale.codes, + }; + } + + function isInt(n) { + return n % 1 === 0; + } + function padStart(val, len) { + let s = String(val); + return '000'.substr(0, len - s.length) + s; + } + + const INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds']; + const PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/; + // Parsing and Creation + function createDuration(input, unit) { + if (typeof input === 'string') { + return parseString(input); + } + if (typeof input === 'object' && input) { // non-null object + return parseObject(input); + } + if (typeof input === 'number') { + return parseObject({ [unit || 'milliseconds']: input }); + } + return null; + } + function parseString(s) { + let m = PARSE_RE.exec(s); + if (m) { + let sign = m[1] ? -1 : 1; + return { + years: 0, + months: 0, + days: sign * (m[2] ? parseInt(m[2], 10) : 0), + milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours + (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes + (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds + (m[6] ? parseInt(m[6], 10) : 0) // ms + ), + }; + } + return null; + } + function parseObject(obj) { + let duration = { + years: obj.years || obj.year || 0, + months: obj.months || obj.month || 0, + days: obj.days || obj.day || 0, + milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours + (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes + (obj.seconds || obj.second || 0) * 1000 + // seconds + (obj.milliseconds || obj.millisecond || obj.ms || 0), // ms + }; + let weeks = obj.weeks || obj.week; + if (weeks) { + duration.days += weeks * 7; + duration.specifiedWeeks = true; + } + return duration; + } + // Equality + function durationsEqual(d0, d1) { + return d0.years === d1.years && + d0.months === d1.months && + d0.days === d1.days && + d0.milliseconds === d1.milliseconds; + } + function asCleanDays(dur) { + if (!dur.years && !dur.months && !dur.milliseconds) { + return dur.days; + } + return 0; + } + // Simple Math + function addDurations(d0, d1) { + return { + years: d0.years + d1.years, + months: d0.months + d1.months, + days: d0.days + d1.days, + milliseconds: d0.milliseconds + d1.milliseconds, + }; + } + function subtractDurations(d1, d0) { + return { + years: d1.years - d0.years, + months: d1.months - d0.months, + days: d1.days - d0.days, + milliseconds: d1.milliseconds - d0.milliseconds, + }; + } + function multiplyDuration(d, n) { + return { + years: d.years * n, + months: d.months * n, + days: d.days * n, + milliseconds: d.milliseconds * n, + }; + } + // Conversions + // "Rough" because they are based on average-case Gregorian months/years + function asRoughYears(dur) { + return asRoughDays(dur) / 365; + } + function asRoughMonths(dur) { + return asRoughDays(dur) / 30; + } + function asRoughDays(dur) { + return asRoughMs(dur) / 864e5; + } + function asRoughMinutes(dur) { + return asRoughMs(dur) / (1000 * 60); + } + function asRoughSeconds(dur) { + return asRoughMs(dur) / 1000; + } + function asRoughMs(dur) { + return dur.years * (365 * 864e5) + + dur.months * (30 * 864e5) + + dur.days * 864e5 + + dur.milliseconds; + } + // Advanced Math + function wholeDivideDurations(numerator, denominator) { + let res = null; + for (let i = 0; i < INTERNAL_UNITS.length; i += 1) { + let unit = INTERNAL_UNITS[i]; + if (denominator[unit]) { + let localRes = numerator[unit] / denominator[unit]; + if (!isInt(localRes) || (res !== null && res !== localRes)) { + return null; + } + res = localRes; + } + else if (numerator[unit]) { + // needs to divide by something but can't! + return null; + } + } + return res; + } + function greatestDurationDenominator(dur) { + let ms = dur.milliseconds; + if (ms) { + if (ms % 1000 !== 0) { + return { unit: 'millisecond', value: ms }; + } + if (ms % (1000 * 60) !== 0) { + return { unit: 'second', value: ms / 1000 }; + } + if (ms % (1000 * 60 * 60) !== 0) { + return { unit: 'minute', value: ms / (1000 * 60) }; + } + if (ms) { + return { unit: 'hour', value: ms / (1000 * 60 * 60) }; + } + } + if (dur.days) { + if (dur.specifiedWeeks && dur.days % 7 === 0) { + return { unit: 'week', value: dur.days / 7 }; + } + return { unit: 'day', value: dur.days }; + } + if (dur.months) { + return { unit: 'month', value: dur.months }; + } + if (dur.years) { + return { unit: 'year', value: dur.years }; + } + return { unit: 'millisecond', value: 0 }; + } + + // timeZoneOffset is in minutes + function buildIsoString(marker, timeZoneOffset, stripZeroTime = false) { + let s = marker.toISOString(); + s = s.replace('.000', ''); + if (stripZeroTime) { + s = s.replace('T00:00:00Z', ''); + } + if (s.length > 10) { + if (timeZoneOffset == null) { + s = s.replace('Z', ''); + } + else if (timeZoneOffset !== 0) { + s = s.replace('Z', formatTimeZoneOffset(timeZoneOffset, true)); + } + } + return s; + } + function formatDayString(marker) { + return marker.toISOString().replace(/T.*$/, ''); + } + function formatIsoMonthStr(marker) { + return marker.toISOString().match(/^\d{4}-\d{2}/)[0]; + } + function formatIsoTimeString(marker) { + return padStart(marker.getUTCHours(), 2) + ':' + + padStart(marker.getUTCMinutes(), 2) + ':' + + padStart(marker.getUTCSeconds(), 2); + } + function formatTimeZoneOffset(minutes, doIso = false) { + let sign = minutes < 0 ? '-' : '+'; + let abs = Math.abs(minutes); + let hours = Math.floor(abs / 60); + let mins = Math.round(abs % 60); + if (doIso) { + return `${sign + padStart(hours, 2)}:${padStart(mins, 2)}`; + } + return `GMT${sign}${hours}${mins ? `:${padStart(mins, 2)}` : ''}`; + } + function joinDateTimeFormatParts(parts) { + let s = ''; + for (const part of parts) { + s += part.value; + } + return s; + } + + const ISO_RE = /^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/; + function parse(str) { + let m = ISO_RE.exec(str); + if (m) { + let marker = new Date(Date.UTC(Number(m[1]), m[3] ? Number(m[3]) - 1 : 0, Number(m[5] || 1), Number(m[7] || 0), Number(m[8] || 0), Number(m[10] || 0), m[12] ? Number(`0.${m[12]}`) * 1000 : 0)); + if (isValidDate(marker)) { + let timeZoneOffset = null; + if (m[13]) { + timeZoneOffset = (m[15] === '-' ? -1 : 1) * (Number(m[16] || 0) * 60 + + Number(m[18] || 0)); + } + return { + marker, + isTimeUnspecified: !m[6], + timeZoneOffset, + }; + } + } + return null; + } + + class DateEnv { + constructor(settings) { + this.timeZone = settings.timeZone; + this.calendarSystem = createCalendarSystem(settings.calendarSystem); + this.locale = settings.locale; + this.weekDow = settings.locale.week.dow; + this.weekDoy = settings.locale.week.doy; + if (settings.weekNumberCalculation === 'ISO') { + this.weekDow = 1; + this.weekDoy = 4; + } + if (typeof settings.firstDay === 'number') { + this.weekDow = settings.firstDay; + } + if (typeof settings.weekNumberCalculation === 'function') { + this.weekNumberFunc = settings.weekNumberCalculation; + } + this.weekTextLong = settings.weekTextLong; + this.weekTextShort = settings.weekTextShort ?? settings.weekTextLong; + this.cmdFormatter = settings.cmdFormatter; + } + // Creating / Parsing + createMarker(input) { + let meta = this.createMarkerMeta(input); + if (meta === null) { + return null; + } + return meta.marker; + } + createNowMarker() { + return this.timestampToMarker(new Date().valueOf()); + } + createMarkerMeta(input) { + if (typeof input === 'string') { + return this.parse(input); + } + let marker = null; + if (typeof input === 'number') { + marker = this.timestampToMarker(input); + } + else if (input instanceof Date) { + input = input.valueOf(); + if (!isNaN(input)) { + marker = this.timestampToMarker(input); + } + } + else if (Array.isArray(input)) { + marker = arrayToUtcDate(input); + } + if (marker === null || !isValidDate(marker)) { + return null; + } + return { marker, isTimeUnspecified: false }; + } + parse(s) { + let parts = parse(s); + if (parts === null) { + return null; + } + let { marker } = parts; + if (parts.timeZoneOffset !== null) { + marker = this.timestampToMarker(marker.valueOf() - parts.timeZoneOffset * 60 * 1000); + } + return { marker, isTimeUnspecified: parts.isTimeUnspecified }; + } + // Accessors + getYear(marker) { + return this.calendarSystem.getMarkerYear(marker); + } + getMonth(marker) { + return this.calendarSystem.getMarkerMonth(marker); + } + getDay(marker) { + return this.calendarSystem.getMarkerDay(marker); + } + // Adding / Subtracting + add(marker, dur) { + let a = this.calendarSystem.markerToArray(marker); + a[0] += dur.years; + a[1] += dur.months; + a[2] += dur.days; + a[6] += dur.milliseconds; + return this.calendarSystem.arrayToMarker(a); + } + subtract(marker, dur) { + let a = this.calendarSystem.markerToArray(marker); + a[0] -= dur.years; + a[1] -= dur.months; + a[2] -= dur.days; + a[6] -= dur.milliseconds; + return this.calendarSystem.arrayToMarker(a); + } + addYears(marker, n) { + let a = this.calendarSystem.markerToArray(marker); + a[0] += n; + return this.calendarSystem.arrayToMarker(a); + } + addMonths(marker, n) { + let a = this.calendarSystem.markerToArray(marker); + a[1] += n; + return this.calendarSystem.arrayToMarker(a); + } + // Diffing Whole Units + diffWholeYears(m0, m1) { + let { calendarSystem } = this; + if (timeAsMs(m0) === timeAsMs(m1) && + calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1) && + calendarSystem.getMarkerMonth(m0) === calendarSystem.getMarkerMonth(m1)) { + return calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0); + } + return null; + } + diffWholeMonths(m0, m1) { + let { calendarSystem } = this; + if (timeAsMs(m0) === timeAsMs(m1) && + calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1)) { + return (calendarSystem.getMarkerMonth(m1) - calendarSystem.getMarkerMonth(m0)) + + (calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0)) * 12; + } + return null; + } + // Range / Duration + greatestWholeUnit(m0, m1) { + let n = this.diffWholeYears(m0, m1); + if (n !== null) { + return { unit: 'year', value: n }; + } + n = this.diffWholeMonths(m0, m1); + if (n !== null) { + return { unit: 'month', value: n }; + } + n = diffWholeWeeks(m0, m1); + if (n !== null) { + return { unit: 'week', value: n }; + } + n = diffWholeDays(m0, m1); + if (n !== null) { + return { unit: 'day', value: n }; + } + n = diffHours(m0, m1); + if (isInt(n)) { + return { unit: 'hour', value: n }; + } + n = diffMinutes(m0, m1); + if (isInt(n)) { + return { unit: 'minute', value: n }; + } + n = diffSeconds(m0, m1); + if (isInt(n)) { + return { unit: 'second', value: n }; + } + return { unit: 'millisecond', value: m1.valueOf() - m0.valueOf() }; + } + countDurationsBetween(m0, m1, d) { + // TODO: can use greatestWholeUnit + let diff; + if (d.years) { + diff = this.diffWholeYears(m0, m1); + if (diff !== null) { + return diff / asRoughYears(d); + } + } + if (d.months) { + diff = this.diffWholeMonths(m0, m1); + if (diff !== null) { + return diff / asRoughMonths(d); + } + } + if (d.days) { + diff = diffWholeDays(m0, m1); + if (diff !== null) { + return diff / asRoughDays(d); + } + } + return (m1.valueOf() - m0.valueOf()) / asRoughMs(d); + } + // Start-Of + // these DON'T return zoned-dates. only UTC start-of dates + startOf(m, unit) { + if (unit === 'year') { + return this.startOfYear(m); + } + if (unit === 'month') { + return this.startOfMonth(m); + } + if (unit === 'week') { + return this.startOfWeek(m); + } + if (unit === 'day') { + return startOfDay(m); + } + if (unit === 'hour') { + return startOfHour(m); + } + if (unit === 'minute') { + return startOfMinute(m); + } + if (unit === 'second') { + return startOfSecond(m); + } + return null; + } + startOfYear(m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + ]); + } + startOfMonth(m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + this.calendarSystem.getMarkerMonth(m), + ]); + } + startOfWeek(m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + this.calendarSystem.getMarkerMonth(m), + m.getUTCDate() - ((m.getUTCDay() - this.weekDow + 7) % 7), + ]); + } + // Week Number + computeWeekNumber(marker) { + if (this.weekNumberFunc) { + return this.weekNumberFunc(this.toDate(marker)); + } + return weekOfYear(marker, this.weekDow, this.weekDoy); + } + formatToParts(marker, formatter) { + return formatter.formatToParts({ + marker, + timeZoneOffset: this.offsetForMarker(marker), + }, this); + } + formatRangeToParts(start, end, formatter, dateOptions = {}) { + if (dateOptions.isEndExclusive) { + end = addMs(end, -1); + } + return formatter.formatRangeToParts({ + marker: start, + timeZoneOffset: this.offsetForMarker(start), + }, { + marker: end, + timeZoneOffset: this.offsetForMarker(end), + }, this); + } + /* + DUMB: the omitTime arg is dumb. if we omit the time, we want to omit the timezone offset. and if we do that, + might as well use buildIsoString or some other util directly + */ + formatIso(marker, extraOptions = {}) { + let timeZoneOffset = null; + if (!extraOptions.omitTimeZoneOffset) { + timeZoneOffset = this.offsetForMarker(marker); + } + return buildIsoString(marker, timeZoneOffset, extraOptions.omitTime); + } + // TimeZone + timestampToMarker(ms) { + if (this.timeZone === 'local') { + return arrayToUtcDate(dateToLocalArray(new Date(ms))); + } + if (this.timeZone === 'UTC') { + return new Date(ms); + } + const zdt = toZonedDateTimeISO(fromEpochMilliseconds(ms), this.timeZone); + return new Date(// a "Date Marker", which is like PlainDateTime + Date.UTC(zdt.year, zdt.month - 1, zdt.day, zdt.hour, zdt.minute, zdt.second, zdt.millisecond)); + } + offsetForMarker(m) { + if (this.timeZone === 'local') { + return -arrayToLocalDate(dateToUtcArray(m)).getTimezoneOffset(); // convert "inverse" offset to "normal" offset + } + if (this.timeZone === 'UTC') { + return 0; + } + return offsetNanoseconds(toZonedDateTime(create(m.getUTCFullYear(), m.getUTCMonth() + 1, m.getUTCDate(), m.getUTCHours(), m.getUTCMinutes(), m.getUTCSeconds(), m.getUTCMilliseconds()), this.timeZone)) / (1000000000 * 60); + } + // Conversion + toDate(m) { + if (this.timeZone === 'local') { + return arrayToLocalDate(dateToUtcArray(m)); + } + if (this.timeZone === 'UTC') { + return new Date(m.valueOf()); // make sure it's a copy + } + return new Date(toZonedDateTime(create(m.getUTCFullYear(), m.getUTCMonth() + 1, m.getUTCDate(), m.getUTCHours(), m.getUTCMinutes(), m.getUTCSeconds(), m.getUTCMilliseconds()), this.timeZone).epochMilliseconds); + } + } + + const EXTENDED_SETTINGS = new Set([ + 'week', + 'meridiem', + 'omitZeroMinute', + 'omitCommas', + 'forceCommas', + 'omitTrailing', + 'weekdayJustify', + ]); + const MERIDIEM_RE = /([ap])\.?m\.?/i; + const COMMA_RE = /,/g; + const LTR_RE = /\u200e/g; // control character + const TRAILING_RE = /[\s.,]+$/; + const WHITESPACE_ONLY_RE = /^\s+$/; + class NativeDateFormatter { + constructor(options) { + const standardOptions = {}; + const extendedOptions = {}; + for (const name in options) { + if (EXTENDED_SETTINGS.has(name)) { + extendedOptions[name] = options[name]; + } + else { + standardOptions[name] = options[name]; + } + } + if (standardOptions.timeZoneName === 'long') { + standardOptions.timeZoneName = 'short'; + } + this.timeZoneOnly = Object.keys(standardOptions).length === 1 && + standardOptions.timeZoneName === 'short'; + this.weekOnly = Boolean(!Object.keys(standardOptions).length && extendedOptions.week); + if (!this.timeZoneOnly) { + if (standardOptions.timeZoneName) { + if (!standardOptions.hour) { + standardOptions.hour = '2-digit'; + } + if (!standardOptions.minute) { + standardOptions.minute = '2-digit'; + } + } + if (extendedOptions.omitZeroMinute && + (standardOptions.second || standardOptions.fractionalSecondDigits)) { + delete extendedOptions.omitZeroMinute; + } + standardOptions.timeZone = 'UTC'; + } + this.standardOptions = standardOptions; + this.extendedOptions = extendedOptions; + } + formatToParts(date, context) { + const { standardOptions, extendedOptions } = this; + if (this.timeZoneOnly) { + return [{ + type: 'timeZoneName', + value: formatTimeZoneOffset(date.timeZoneOffset), + }]; + } + if (this.weekOnly) { + return formatWeekNumberParts(context.computeWeekNumber(date.marker), context.weekTextLong, context.weekTextShort, context.locale, extendedOptions.week); + } + const { normalFormat, zeroFormat } = this.getFormats(context); + const format = (zeroFormat && !date.marker.getUTCMinutes()) + ? zeroFormat + : normalFormat; + const parts = format.formatToParts(date.marker); + return postProcessParts(parts, date, standardOptions, extendedOptions); + } + formatRangeToParts(start, end, context) { + const { standardOptions, extendedOptions } = this; + if (this.timeZoneOnly || this.weekOnly) { + return this.formatToParts(start, context).map((part) => { + return { + source: part.type === 'literal' ? 'shared' : 'startRange', + ...part, + }; + }); + } + const { normalFormat, zeroFormat } = this.getFormats(context); + const format = (zeroFormat && !start.marker.getUTCMinutes() && !end.marker.getUTCMinutes()) + ? zeroFormat + : normalFormat; + const parts = format.formatRangeToParts(start.marker, end.marker); + return postProcessRangeParts(parts, start, end, standardOptions, extendedOptions); + } + getFormats(context) { + if (this.cachedContext !== context) { + const { standardOptions, extendedOptions } = this; + const { codes } = context.locale; + const normalFormat = new Intl.DateTimeFormat(codes, standardOptions); + let zeroFormat; + if (extendedOptions.omitZeroMinute) { + const zeroProps = { ...standardOptions }; + delete zeroProps.minute; + zeroFormat = new Intl.DateTimeFormat(codes, zeroProps); + } + this.cachedContext = context; + this.cachedFormats = { normalFormat, zeroFormat }; + } + return this.cachedFormats; + } + } + function processPartsLoop(parts, extendedOptions, getTzValue) { + let anyTzInjected = false; + let priorLiteral; + for (const part of parts) { + const isLiteral = part.type === 'literal'; + if (isLiteral || part.type === 'dayPeriod') { + let s = part.value; + s = s.replace(LTR_RE, ''); + if (extendedOptions.omitCommas) { + s = s.replace(COMMA_RE, ''); + } + if (!isLiteral) { + const { meridiem } = extendedOptions; + if (meridiem === false) { + s = s.replace(MERIDIEM_RE, ''); + } + else if (meridiem === 'narrow') { + s = s.replace(MERIDIEM_RE, (_m0, m1) => m1.toLocaleLowerCase()); + } + else if (meridiem === 'short') { + s = s.replace(MERIDIEM_RE, (_m0, m1) => `${m1.toLocaleLowerCase()}m`); + } + else if (meridiem === 'lowercase') { + s = s.replace(MERIDIEM_RE, (m0) => m0.toLocaleLowerCase()); + } + if (priorLiteral) { + priorLiteral.value = priorLiteral.value.trimEnd(); + } + } + part.value = s; + } + else if (part.type === 'timeZoneName') { + const tzValue = getTzValue(part); + if (tzValue != null) { + part.value = tzValue; + anyTzInjected = true; + } + } + priorLiteral = isLiteral ? part : undefined; + } + return { lastLiteral: priorLiteral, anyTzInjected }; + } + function postProcessParts(parts, date, standardOptions, extendedOptions) { + const injectableTz = standardOptions.timeZoneName === 'short' + ? (date.timeZoneOffset == null ? 'UTC' : formatTimeZoneOffset(date.timeZoneOffset)) + : undefined; + const { lastLiteral, anyTzInjected } = processPartsLoop(parts, extendedOptions, () => injectableTz); + if (injectableTz && !anyTzInjected) { + if (lastLiteral) { + lastLiteral.value += ' '; + } + else { + parts.push({ type: 'literal', value: ' ' }); + } + parts.push({ type: 'timeZoneName', value: injectableTz }); + } + if (extendedOptions.weekdayJustify && + parts.length === 3 && + WHITESPACE_ONLY_RE.test(parts[1].value)) { + if (parts[extendedOptions.weekdayJustify === 'start' ? 2 : 0].type === 'weekday') { + parts.reverse(); + } + } + if (extendedOptions.forceCommas) { + for (const part of parts) { + if (part.type === 'literal' && WHITESPACE_ONLY_RE.test(part.value)) { + part.value = `,${part.value}`; + } + } + } + if (extendedOptions.omitTrailing) { + stripTrailingLiteral(parts); + } + return parts.filter((part) => part.value); + } + function postProcessRangeParts(parts, start, end, standardOptions, extendedOptions) { + const injectTz = standardOptions.timeZoneName === 'short'; + processPartsLoop(parts, extendedOptions, (part) => { + if (!injectTz) + return undefined; + const offset = part.source === 'endRange' ? end.timeZoneOffset : start.timeZoneOffset; + return offset == null ? 'UTC' : formatTimeZoneOffset(offset); + }); + if (extendedOptions.forceCommas) { + for (const part of parts) { + if (part.type === 'literal' && WHITESPACE_ONLY_RE.test(part.value)) { + part.value = `,${part.value}`; + } + } + } + if (extendedOptions.omitTrailing) { + stripTrailingLiteral(parts); + } + return parts.filter((part) => part.value); + } + function stripTrailingLiteral(parts) { + const lastPart = parts[parts.length - 1]; + if (lastPart?.type === 'literal') { + lastPart.value = lastPart.value.replace(TRAILING_RE, ''); + if (!lastPart.value) { + parts.pop(); + } + } + } + function formatWeekNumberParts(num, weekTextLong, weekTextShort, locale, display) { + const parts = []; + if (display === 'long') { + parts.push({ type: 'literal', value: weekTextLong }); + } + else if (display === 'short' || display === 'narrow') { + parts.push({ type: 'literal', value: weekTextShort }); + } + if (display === 'long' || display === 'short') { + parts.push({ type: 'literal', value: ' ' }); + } + parts.push({ + type: 'week', + value: locale.simpleNumberFormat.format(num), + }); + if (locale.options.direction === 'rtl') { + parts.reverse(); + } + return parts; + } + + class CmdDateFormatter { + constructor(cmdStr) { + this.cmdStr = cmdStr; + } + formatToParts(date, context) { + const res = context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(date, null, context)); + if (Array.isArray(res)) { + return res; + } + return [{ type: 'literal', value: res }]; + } + formatRangeToParts(start, end, context) { + const res = context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(start, end, context)); + if (Array.isArray(res)) { + return res.map((part) => ({ + source: 'shared', + ...part, + })); + } + return [{ source: 'shared', type: 'literal', value: res }]; + } + } + + class FuncDateFormatter { + constructor(func) { + this.func = func; + } + formatToParts(date, context) { + const str = this.func(createVerboseFormattingArg(date, null, context)); + return [{ type: 'literal', value: str }]; + } + formatRangeToParts(start, end, context) { + const str = this.func(createVerboseFormattingArg(start, end, context)); + return [{ source: 'shared', type: 'literal', value: str }]; + } + } + + var classNames = {"popoverZ":"fc-11","isolate":"fc-HY","borderBoxRoot":"fc-jz","notAllowed":"fc-j2","noScrollbars":"fc-Ki","noShrink":"fc-ST","calendarScreenRoot":"fc-Z9","safeTiles":"fc-1g","calendarPrintRoot":"fc-HC","cursorPointer":"fc-ZB","cursorResizeT":"fc-Ld","cursorResizeB":"fc-lJ","cursorResizeS":"fc-Zm","cursorResizeE":"fc-n5","cursorColResizer":"fc-Px","hit":"fc-QK","hitX":"fc-eh","hitY":"fc-M9","hitXSkinny":"fc-SC","selectNone":"fc-hi","invisible":"fc-F0","borderNone":"fc-6s","borderOnlyT":"fc-8B","borderOnlyB":"fc-RJ","borderOnlyS":"fc-Hm","borderOnlyE":"fc-ge","borderlessX":"fc-KC","borderlessY":"fc-mE","fakeBorderS":"fc-FA","flexRow":"fc-5g","flexCol":"fc-Fa","grow":"fc-16","liquid":"fc-Im","minHeight0":"fc-tZ","liquidX":"fc-Pw","printRoot":"fc-kE","printHeader":"fc-Xg","noPadding":"fc-np","noMargin":"fc-gd","noMarginY":"fc-hB","noMarginX":"fc-uk","whiteSpaceNoWrap":"fc-he","whiteSpacePre":"fc-Bv","overflowAnchorNone":"fc-U2","crop":"fc-bB","cropNowrap":"fc-pp","rel":"fc-8A","abs":"fc-7t","start0":"fc-pK","fill":"fc-wP","fillTop":"fc-yW","fillX":"fc-5j","fillY":"fc-1K","fillStart":"fc-bH","sticky":"fc-dY","stickyT":"fc-aX","stickyS":"fc-pZ","tableHeaderSticky":"fc-Mw","contentBox":"fc-CU","offscreen":"fc-jm","alignCenter":"fc-oO","alignStart":"fc-MM","alignEnd":"fc-Qk","footerScrollbarSticky":"fc-Q6","footerScrollbar":"fc-bD","breakInsideAvoid":"fc-lF","printSiblingRow":"fc-Vf","z0":"fc-BP","z1":"fc-XV","focusZ2":"fc-Ah","internalTimelineSlot":"fc-vz","internalEvent":"fc-4B","internalEventMirror":"fc-D7","internalEventDraggable":"fc-wp","internalEventSelected":"fc-Ex","internalEventResizable":"fc-wl","internalEventResizer":"fc-yM","internalEventResizerStart":"fc-2f","internalEventResizerEnd":"fc-zR","internalBgEvent":"fc-0j","internalMoreLink":"fc-M7","internalNavLink":"fc-tb","internalPopover":"fc-ew","internalView":"fc-IZ","internalScroller":"fc-UH"}; + + function joinClassNames(...args) { + return args.filter(Boolean).join(' '); + } + /* + TODO: dedup with @full-ui/headless-grid somehow + */ + function fracToCssDim(frac) { + return frac * 100 + '%'; + } + + function createFormatter(input) { + if (typeof input === 'object' && input) { // non-null object + return new NativeDateFormatter(input); + } + if (typeof input === 'string') { + return new CmdDateFormatter(input); + } + if (typeof input === 'function') { + return new FuncDateFormatter(input); + } + return null; + } + + function warn(...args) { + console.warn('FullCalendar:', ...args); + } + + /* eslint max-classes-per-file: off */ + const warnedClassNameOptions = {}; + function refineClassName(input, optionName) { + if (!input || typeof input === 'string') { + return input; + } + warnInvalidClassName(optionName); + return ''; + } + function refineClassNameGenerator(input, optionName) { + if (typeof input === 'function') { + return (renderProps) => refineClassName(input(renderProps), optionName); + } + return refineClassName(input, optionName); + } + function warnInvalidClassName(optionName) { + if (!warnedClassNameOptions[optionName]) { + warn(`Invalid option \`${optionName}\`: expected a className string or a falsy value.`); + warnedClassNameOptions[optionName] = true; + } + } + + // Stops a mouse/touch event from doing it's native browser action + function preventDefault(ev) { + ev.preventDefault(); + } + // Event Delegation + // ---------------------------------------------------------------------------------------------------------------- + function buildDelegationHandler(selector, handler) { + return (ev) => { + let matchedChild = ev.target.closest(selector); + if (matchedChild) { + handler.call(matchedChild, ev, matchedChild); + } + }; + } + function listenBySelector(container, eventType, selector, handler) { + let attachedHandler = buildDelegationHandler(selector, handler); + container.addEventListener(eventType, attachedHandler); + return () => { + container.removeEventListener(eventType, attachedHandler); + }; + } + function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) { + let currentMatchedChild; + return listenBySelector(container, 'mouseover', selector, (mouseOverEv, matchedChild) => { + if (matchedChild !== currentMatchedChild) { + currentMatchedChild = matchedChild; + onMouseEnter(mouseOverEv, matchedChild); + let realOnMouseLeave = (mouseLeaveEv) => { + currentMatchedChild = null; + onMouseLeave(mouseLeaveEv, matchedChild); + matchedChild.removeEventListener('mouseleave', realOnMouseLeave); + }; + // listen to the next mouseleave, and then unattach + matchedChild.addEventListener('mouseleave', realOnMouseLeave); + } + }); + } + // Animation + // ---------------------------------------------------------------------------------------------------------------- + const transitionEventNames = [ + 'webkitTransitionEnd', + 'otransitionend', + 'oTransitionEnd', + 'msTransitionEnd', + 'transitionend', + ]; + // triggered only when the next single subsequent transition finishes + function whenTransitionDone(el, callback) { + let realCallback = (ev) => { + callback(ev); + transitionEventNames.forEach((eventName) => { + el.removeEventListener(eventName, realCallback); + }); + }; + transitionEventNames.forEach((eventName) => { + el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes + }); + } + // ARIA workarounds + // ---------------------------------------------------------------------------------------------------------------- + function createAriaClickAttrs(handler) { + return { + onClick: handler, + ...createAriaKeyboardAttrs(handler), + }; + } + function createAriaKeyboardAttrs(handler) { + return { + tabIndex: 0, + onKeyDown(ev) { + if (ev.key === 'Enter' || ev.key === ' ') { + handler(ev); + ev.preventDefault(); // if space, don't scroll down page + } + }, + }; + } + + let guidNumber = 0; + function guid() { + guidNumber += 1; + return String(guidNumber); + } + /* FullCalendar-specific DOM Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + // Make the mouse cursor express that an event is not allowed in the current area + function disableCursor() { + document.body.classList.add(classNames.notAllowed); + } + // Returns the mouse cursor to its original look + function enableCursor() { + document.body.classList.remove(classNames.notAllowed); + } + /* Selection + ----------------------------------------------------------------------------------------------------------------------*/ + function preventSelection(el) { + el.style.userSelect = 'none'; + el.style.webkitUserSelect = 'none'; + el.addEventListener('selectstart', preventDefault); + } + function allowSelection(el) { + el.style.userSelect = ''; + el.style.webkitUserSelect = ''; + el.removeEventListener('selectstart', preventDefault); + } + /* Context Menu + ----------------------------------------------------------------------------------------------------------------------*/ + function preventContextMenu(el) { + el.addEventListener('contextmenu', preventDefault); + } + function allowContextMenu(el) { + el.removeEventListener('contextmenu', preventDefault); + } + function parseFieldSpecs(input) { + let specs = []; + let tokens = []; + let i; + let token; + if (typeof input === 'string') { + tokens = input.split(/\s*,\s*/); + } + else if (typeof input === 'function') { + tokens = [input]; + } + else if (Array.isArray(input)) { + tokens = input; + } + for (i = 0; i < tokens.length; i += 1) { + token = tokens[i]; + if (typeof token === 'string') { + specs.push(token.charAt(0) === '-' ? + { field: token.substring(1), order: -1 } : + { field: token, order: 1 }); + } + else if (typeof token === 'function') { + specs.push({ func: token }); + } + } + return specs; + } + function compareByFieldSpecs(obj0, obj1, fieldSpecs) { + let i; + let cmp; + for (i = 0; i < fieldSpecs.length; i += 1) { + cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]); + if (cmp) { + return cmp; + } + } + return 0; + } + function compareByFieldSpec(obj0, obj1, fieldSpec) { + if (fieldSpec.func) { + return fieldSpec.func(obj0, obj1); + } + return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field]) + * (fieldSpec.order || 1); + } + function flexibleCompare(a, b) { + if (!a && !b) { + return 0; + } + if (b == null) { + return -1; + } + if (a == null) { + return 1; + } + if (typeof a === 'string' || typeof b === 'string') { + return String(a).localeCompare(String(b)); + } + return a - b; + } + /* String Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + function formatWithOrdinals(formatter, args, fallbackText) { + if (typeof formatter === 'function') { + return formatter(...args); + } + if (typeof formatter === 'string') { // non-blank string + return args.reduce((str, arg, index) => (str.replace('$' + index, arg || '')), formatter); + } + return fallbackText; + } + /* Number Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + function compareNumbers(a, b) { + return a - b; + } + function valuesIdentical(a, b) { + return a === b; + } + function computeViewBorderless(options) { + const borderless = options.borderless; + return { + borderlessX: Boolean(options.borderlessX ?? borderless), + borderlessTop: Boolean(options.borderlessTop ?? borderless), + borderlessBottom: Boolean(options.borderlessBottom ?? borderless), + }; + } + + const { hasOwnProperty } = Object.prototype; + // Filter / Map + // ------------------------------------------------------------------------------------------------- + function filterHash(hash, func) { + let filtered = {}; + for (let key in hash) { + if (func(hash[key], key)) { + filtered[key] = hash[key]; + } + } + return filtered; + } + function mapHash(hash, func) { + let newHash = {}; + for (let key in hash) { + newHash[key] = func(hash[key], key); + } + return newHash; + } + // Conversion + // ------------------------------------------------------------------------------------------------- + // Can't use Object.values yet because no es2015 support + // TODO: reassess browser support + // https://caniuse.com/?search=object.values + function hashValuesToArray(obj) { + let a = []; + for (let key in obj) { + a.push(obj[key]); + } + return a; + } + // TODO: rename to stringArrayToHash or something + function arrayToHash(a) { + let hash = {}; + for (let item of a) { + hash[item] = true; + } + return hash; + } + // Equality + // ------------------------------------------------------------------------------------------------- + function isMaybePropsEqualDepth1(props0, props1) { + if (typeof props0 === 'object' && props0 && // non-null object + typeof props1 === 'object' && props1 // non-null object + ) { + return isPropsEqualWithFunc(props0, props1, isPropsEqualShallow); + } + return props0 === props1; + } + function isPropsEqualWithFunc(props0, props1, valuesEqual) { + if (props0 === props1) { + return true; + } + for (let key in props0) { + if (hasOwnProperty.call(props0, key)) { + if (!(key in props1)) { + return false; + } + } + } + for (let key in props1) { + if (hasOwnProperty.call(props1, key)) { + if (!(key in props0) || !valuesEqual(props0[key], props1[key], key)) { + return false; + } + } + } + return true; + } + function isMaybePropsEqualShallow(props0, props1) { + if (typeof props0 === 'object' && + typeof props1 === 'object' && + props0 && props1 // both non-null objects + ) { + return isPropsEqualShallow(props0, props1); + } + return props0 === props1; + } + function isPropsEqualShallow(props0, props1) { + return isPropsEqualWithFunc(props0, props1, valuesIdentical); + } + function isPropsEqualWithMap(props0, props1, equalityFuncMap) { + return isPropsEqualWithFunc(props0, props1, (val0, val1, key) => { + const equalityFunc = equalityFuncMap[key]; + const isEqual = equalityFunc + ? equalityFunc(val0, val1) + : val0 === val1; + // if (debugMessage && !isEqual) { + // console.log( + // debugMessage, key, 'NOT EQUAL', 'rerunning...', + // equalityFunc + // ? equalityFunc(val0, val1) + // : val0 === val1 + // ) + // } + return isEqual; + }); + } + /* + Returns array of keys + */ + function getUnequalProps(props0, props1) { + let keys = []; + for (let key in props0) { + if (hasOwnProperty.call(props0, key)) { + if (!(key in props1)) { + keys.push(key); + } + } + } + for (let key in props1) { + if (hasOwnProperty.call(props1, key)) { + if (props0[key] !== props1[key]) { + keys.push(key); + } + } + } + return keys; + } + // Merge + // ------------------------------------------------------------------------------------------------- + function mergeMaybePropsDepth1(props0, props1) { + if (!props0) { + return props1; + } + return mergePropsWithFunc(props0, props1, mergePropsShallow); + } + function mergePropsWithFunc(props0, props1, mergeValues) { + const dest = {}; + for (let key in props0) { + if (hasOwnProperty.call(props0, key)) { + if (!(key in props1)) { + dest[key] = props0[key]; + } + } + } + for (let key in props1) { + if (hasOwnProperty.call(props1, key)) { + if (!(key in props0)) { + dest[key] = props1[key]; + } + else { + dest[key] = mergeValues(props0[key], props1[key]); + } + } + } + return dest; + } + function mergePropsShallow(props0, props1) { + return Object.assign({}, props0, props1); + } + + function removeExact(array, exactItem) { + let removeCnt = 0; + let i = 0; + while (i < array.length) { + if (array[i] === exactItem) { + array.splice(i, 1); + removeCnt += 1; + } + else { + i += 1; + } + } + return removeCnt; + } + function isMaybeArraysEqual(array0, array1) { + if (Array.isArray(array0) && Array.isArray(array1)) { + return isArraysEqual(array0, array1); + } + return array0 === array1; + } + function isArraysEqual(array0, array1, itemsEqual = valuesIdentical) { + if (array0 === array1) { + return true; + } + let len = array0.length; + let i; + if (len !== array1.length) { // not array? or not same length? + return false; + } + for (i = 0; i < len; i += 1) { + if (!itemsEqual(array0[i], array1[i])) { + return false; + } + } + return true; + } + + // base options + // ------------ + const BASE_OPTION_REFINERS = { + navLinkDayClick: identity, + navLinkWeekClick: identity, + duration: createDuration, + buttons: identity, + toolbarElements: identity, + prevText: String, + nextText: String, + prevYearText: String, + nextYearText: String, + todayText: String, + yearText: String, + monthText: String, + weekTextLong: String, + weekTextShort: String, + dayText: String, + listText: identity, + todayHint: identity, + prevHint: identity, + nextHint: identity, + // TODO: make type for hint input + buttonDisplay: identity, + buttonGroupClass: refineClassNameGenerator, + buttonClass: refineClassNameGenerator, + defaultAllDayEventDuration: createDuration, + defaultTimedEventDuration: createDuration, + nextDayThreshold: createDuration, + scrollTime: createDuration, + scrollTimeReset: Boolean, + slotMinTime: createDuration, + slotMaxTime: createDuration, + popoverFormat: createFormatter, + slotDuration: createDuration, + snapDuration: createDuration, + headerToolbar: identity, + footerToolbar: identity, + forceEventDuration: Boolean, + // TODO: move to timegrid + dayLaneClass: refineClassNameGenerator, + dayLaneInnerClass: refineClassNameGenerator, + dayLaneDidMount: identity, + dayLaneWillUnmount: identity, + initialView: String, + aspectRatio: Number, + weekends: Boolean, + weekNumberCalculation: identity, + weekNumbers: Boolean, + weekNumberHeaderClass: refineClassNameGenerator, + weekNumberHeaderInnerClass: refineClassNameGenerator, + weekNumberHeaderContent: identity, + weekNumberHeaderDidMount: identity, + weekNumberHeaderWillUnmount: identity, + inlineWeekNumberClass: refineClassNameGenerator, + inlineWeekNumberContent: identity, + inlineWeekNumberDidMount: identity, + inlineWeekNumberWillUnmount: identity, + editable: Boolean, + controller: identity, + nowIndicator: Boolean, + nowIndicatorSnap: identity, + nowIndicatorHeaderClass: refineClassNameGenerator, + nowIndicatorHeaderContent: identity, + nowIndicatorHeaderDidMount: identity, + nowIndicatorHeaderWillUnmount: identity, + nowIndicatorDotClass: refineClassName, + nowIndicatorLineClass: refineClassNameGenerator, + nowIndicatorLineContent: identity, + nowIndicatorLineDidMount: identity, + nowIndicatorLineWillUnmount: identity, + showNonCurrentDates: Boolean, + lazyFetching: Boolean, + startParam: String, + endParam: String, + timeZoneParam: String, + timeZone: String, + locales: identity, + locale: identity, + dragRevertDuration: Number, + dragScroll: Boolean, + allDayMaintainDuration: Boolean, + unselectAuto: Boolean, + dropAccept: identity, // TODO: type draggable + eventOrder: parseFieldSpecs, + eventOrderStrict: Boolean, + eventSlicing: Boolean, // default: true + eventPrintLayout: String, + longPressDelay: Number, + eventDragMinDistance: Number, + expandRows: Boolean, + height: identity, + contentHeight: identity, + direction: String, + colorScheme: String, + weekNumberFormat: createFormatter, + eventResizableFromStart: Boolean, + displayEventTime: Boolean, + displayEventEnd: Boolean, + progressiveEventRendering: Boolean, + businessHours: identity, + initialDate: identity, + now: identity, + eventDataTransform: identity, + tableHeaderSticky: identity, + footerScrollbarSticky: identity, + defaultAllDay: Boolean, + eventSourceFailure: identity, + eventSourceSuccess: identity, + eventDisplay: String, // TODO: give more specific + eventStartEditable: Boolean, + eventDurationEditable: Boolean, + eventOverlap: identity, + eventConstraint: identity, + eventAllow: identity, + eventColor: String, + eventContrastColor: String, + eventDidMount: identity, + eventWillUnmount: identity, + eventContent: identity, + eventClass: refineClassNameGenerator, + eventInnerClass: refineClassNameGenerator, + eventTimeClass: refineClassNameGenerator, + eventTitleClass: refineClassNameGenerator, + eventBeforeClass: refineClassNameGenerator, + eventAfterClass: refineClassNameGenerator, + // + listItemEventClass: refineClassNameGenerator, + listItemEventInnerClass: refineClassNameGenerator, + listItemEventTimeClass: refineClassNameGenerator, + listItemEventTitleClass: refineClassNameGenerator, + listItemEventBeforeClass: refineClassNameGenerator, + listItemEventAfterClass: refineClassNameGenerator, + // + blockEventClass: refineClassNameGenerator, + blockEventInnerClass: refineClassNameGenerator, + blockEventTimeClass: refineClassNameGenerator, + blockEventTitleClass: refineClassNameGenerator, + blockEventBeforeClass: refineClassNameGenerator, + blockEventAfterClass: refineClassNameGenerator, + // + rowEventClass: refineClassNameGenerator, + rowEventInnerClass: refineClassNameGenerator, + rowEventTimeClass: refineClassNameGenerator, + rowEventTitleClass: refineClassNameGenerator, + rowEventTitleSticky: Boolean, + rowEventBeforeClass: refineClassNameGenerator, + rowEventBeforeContent: identity, + rowEventAfterClass: refineClassNameGenerator, + rowEventAfterContent: identity, + // + columnEventClass: refineClassNameGenerator, + columnEventInnerClass: refineClassNameGenerator, + columnEventTimeClass: refineClassNameGenerator, + columnEventTitleClass: refineClassNameGenerator, + columnEventTitleSticky: Boolean, + columnEventBeforeClass: refineClassNameGenerator, + columnEventAfterClass: refineClassNameGenerator, + // + backgroundEventClass: refineClassNameGenerator, + backgroundEventDidMount: identity, + backgroundEventWillUnmount: identity, + backgroundEventContent: identity, + backgroundEventInnerClass: refineClassNameGenerator, + backgroundEventTitleClass: refineClassNameGenerator, + backgroundEventColor: String, + selectConstraint: identity, + selectOverlap: identity, + selectAllow: identity, + droppable: Boolean, + unselectCancel: String, + slotHeaderFormat: identity, + slotLaneClass: refineClassNameGenerator, + slotLaneDidMount: identity, + slotLaneWillUnmount: identity, + slotHeaderClass: refineClassNameGenerator, + slotHeaderInnerClass: refineClassNameGenerator, + slotHeaderContent: identity, + slotHeaderDidMount: identity, + slotHeaderWillUnmount: identity, + slotHeaderAlign: identity, + slotHeaderSticky: identity, + slotHeaderRowClass: refineClassName, + slotHeaderDividerClass: refineClassNameGenerator, + dayMaxEvents: identity, + dayMaxEventRows: identity, + dayMinWidth: Number, + slotHeaderInterval: createDuration, + // in core because more-popover needs it + dayHeaderClass: refineClassNameGenerator, + dayHeaderInnerClass: refineClassNameGenerator, + dayHeaderContent: identity, + dayHeaderDidMount: identity, + dayHeaderWillUnmount: identity, + dayHeaderAlign: identity, + // stickiness for cell-inner-contents laterally. experimental settings + _dayHeaderSticky: identity, + dayHeaderRowClass: refineClassName, + dayHeaderDividerClass: refineClassNameGenerator, + dayRowClass: refineClassName, + dayCellDidMount: identity, + dayCellWillUnmount: identity, + dayCellClass: refineClassNameGenerator, + dayCellInnerClass: refineClassNameGenerator, + dayCellTopContent: identity, + dayCellTopClass: refineClassNameGenerator, + dayCellTopInnerClass: refineClassNameGenerator, + dayCellBottomClass: refineClassNameGenerator, + allDaySlot: Boolean, + allDayText: String, + allDayHeaderClass: refineClassNameGenerator, + allDayHeaderInnerClass: refineClassNameGenerator, + allDayHeaderContent: identity, + allDayHeaderDidMount: identity, + allDayHeaderWillUnmount: identity, + timedText: String, + slotMinWidth: Number, + slotMinHeight: Number, + navLinks: Boolean, + eventTimeFormat: createFormatter, + rerenderDelay: Number, // TODO: move to vanilla right? nah keep here + moreLinkText: identity, // this not enforced :( check others too + moreLinkHint: identity, + selectMinDistance: Number, + selectable: Boolean, + selectLongPressDelay: Number, + eventLongPressDelay: Number, + selectMirror: Boolean, + eventMaxStack: Number, + eventMinHeight: Number, + eventMinWidth: Number, + eventShortHeight: Number, + slotEventOverlap: Boolean, + firstDay: Number, + dayCount: Number, + dateAlignment: String, + dateIncrement: createDuration, + hiddenDays: identity, + fixedWeekCount: Boolean, + validRange: identity, // `this` works? + visibleRange: identity, // `this` works? + titleFormat: identity, + eventInteractive: Boolean, + // only used by list-view, but languages define the value, so we need it in base options + noEventsText: String, + viewHint: identity, + viewChangeHint: String, // for the tab container + navLinkHint: identity, + closeHint: String, + eventsHint: String, + headingLevel: Number, + moreLinkClick: identity, + moreLinkContent: identity, + moreLinkDidMount: identity, + moreLinkWillUnmount: identity, + moreLinkClass: refineClassNameGenerator, + moreLinkInnerClass: refineClassNameGenerator, + // + rowMoreLinkClass: refineClassNameGenerator, + rowMoreLinkInnerClass: refineClassNameGenerator, + // + columnMoreLinkClass: refineClassNameGenerator, + columnMoreLinkInnerClass: refineClassNameGenerator, + navLinkClass: refineClassName, + monthStartFormat: createFormatter, + dayCellFormat: createFormatter, + // for connectors + // (can't be part of plugin system b/c must be provided at runtime) + handleCustomRendering: identity, + customRenderingMetaMap: identity, + customRenderingReplaces: Boolean, + popoverClass: refineClassName, + popoverCloseClass: refineClassName, + popoverCloseContent: identity, + dayNarrowWidth: Number, + borderless: Boolean, + borderlessX: Boolean, + borderlessTop: Boolean, + borderlessBottom: Boolean, + fillerClass: refineClassNameGenerator, + headerToolbarClass: refineClassNameGenerator, + footerToolbarClass: refineClassNameGenerator, + toolbarClass: refineClassNameGenerator, + toolbarSectionClass: refineClassNameGenerator, + toolbarTitleClass: refineClassName, + tableClass: refineClassNameGenerator, + tableHeaderClass: refineClassNameGenerator, + tableBodyClass: refineClassNameGenerator, + nonBusinessHoursClass: refineClassName, + highlightClass: refineClassName, + // daygrid-only + dayHeaders: Boolean, + dayHeaderFormat: createFormatter, + // timegrid-only + allDayDividerClass: refineClassName, + // list-only + listDaysClass: refineClassName, // rename this? + listDayClass: refineClassNameGenerator, + // + listDayFormat: createFalsableFormatter, // defaults specified in list plugins + listDayAltFormat: createFalsableFormatter, // " + // + listDayHeaderDidMount: identity, + listDayHeaderWillUnmount: identity, + listDayHeaderClass: refineClassNameGenerator, + listDayHeaderInnerClass: refineClassNameGenerator, + listDayHeaderContent: identity, + // + listDayBodyClass: refineClassNameGenerator, + // + noEventsClass: refineClassNameGenerator, + noEventsInnerClass: refineClassNameGenerator, + noEventsContent: identity, + noEventsDidMount: identity, + noEventsWillUnmount: identity, + // noEventsText is defined in base options + // multimonth-only + multiMonthMaxColumns: Number, + // + singleMonthMinWidth: Number, + singleMonthTitleFormat: createFormatter, + singleMonthDidMount: identity, + singleMonthWillUnmount: identity, + singleMonthClass: refineClassNameGenerator, + singleMonthHeaderClass: refineClassNameGenerator, + singleMonthHeaderInnerClass: refineClassNameGenerator, + }; + // do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results. + // raw values. + const BASE_OPTION_DEFAULTS = { + buttonDisplay: 'auto', + eventDisplay: 'auto', + defaultTimedEventDuration: '01:00:00', + defaultAllDayEventDuration: { day: 1 }, + forceEventDuration: false, + nextDayThreshold: '00:00:00', + initialView: '', + aspectRatio: 1.35, + weekends: true, + weekNumbers: false, + weekNumberCalculation: 'local', + editable: false, + nowIndicator: false, + scrollTime: '06:00:00', + scrollTimeReset: true, + slotMinTime: '00:00:00', + slotMaxTime: '24:00:00', + showNonCurrentDates: true, + lazyFetching: true, + startParam: 'start', + endParam: 'end', + timeZoneParam: 'timeZone', + timeZone: 'local', // TODO: throw error if given falsy value? + locales: [], + locale: '', // blank values means it will compute based off locales[] + dragRevertDuration: 500, + dragScroll: true, + allDayMaintainDuration: false, + unselectAuto: true, + dropAccept: '*', + eventOrder: 'start,-duration,allDay,title', + eventPrintLayout: 'auto', + popoverFormat: { month: 'long', day: 'numeric', year: 'numeric' }, + longPressDelay: 1000, + eventDragMinDistance: 5, // only applies to mouse + expandRows: false, + navLinks: false, + selectable: false, + eventMinHeight: 15, + eventMinWidth: 30, + eventShortHeight: 30, + monthStartFormat: { month: 'long', day: 'numeric' }, + dayCellFormat: { day: 'numeric', omitTrailing: true }, + headingLevel: 2, // like H2 + outerBorder: true, + dayNarrowWidth: 80, + eventOverlap: true, + slotHeaderAlign: 'start', + slotHeaderSticky: true, + dayHeaderAlign: 'start', + _dayHeaderSticky: true, + rowEventTitleSticky: true, + columnEventTitleSticky: true, + nowIndicatorSnap: 'auto', + // daygrid-only + dayHeaders: true, + }; + // calendar listeners + // ------------------ + const CALENDAR_LISTENER_REFINERS = { + datesSet: identity, + eventsSet: identity, + eventAdd: identity, + eventChange: identity, + eventRemove: identity, + eventClick: identity, // TODO: resource for scheduler???? + eventMouseEnter: identity, + eventMouseLeave: identity, + select: identity, // resource for scheduler???? + unselect: identity, + loading: identity, + // internal + _unmount: identity, + _beforeprint: identity, + _afterprint: identity, + _noDateSelect: identity, + _noEventDrop: identity, + _noEventResize: identity, + _timeScrollRequest: identity, + // interaction-plugin-only + dateClick: identity, + eventDragStart: identity, + eventDragStop: identity, + eventDrop: identity, + eventResizeStart: identity, + eventResizeStop: identity, + eventResize: identity, + drop: identity, + eventReceive: identity, + eventLeave: identity, + }; + // calendar-only options (not for view-specific) + // --------------------------------------------- + const CALENDAR_ONLY_OPTION_REFINERS = { + class: refineClassNameGenerator, + className: refineClassNameGenerator, + viewClass: refineClassNameGenerator, + viewDidMount: identity, + viewWillUnmount: identity, + views: identity, + plugins: identity, + initialEvents: identity, + events: identity, + eventSources: identity, + }; + // view-specific options + // --------------------- + const VIEW_ONLY_OPTION_REFINERS = { + type: String, + component: identity, + class: refineClassNameGenerator, + className: refineClassNameGenerator, + content: identity, + didMount: identity, + willUnmount: identity, + // internal only + buttonTextKey: String, + dateProfileGeneratorClass: identity, + usesMinMaxTime: Boolean, + disallowAmbigTitle: Boolean, + }; + const COMPLEX_OPTION_COMPARATORS = { + // Unfortunately always need 'maybe' to handle undefined inital value, because of CalendarDataManager + dateIncrement: isMaybePropsEqualShallow, + headerToolbar: isMaybePropsEqualShallow, + footerToolbar: isMaybePropsEqualShallow, + buttons: isMaybePropsEqualDepth1, + plugins: isMaybeArraysEqual, + events: isMaybeArraysEqual, + eventSources: isMaybeArraysEqual, + ['resources']: isMaybeArraysEqual, + }; + // util funcs + // ---------------------------------------------------------------------------------------------------- + function refineProps(input, refiners) { + let refined = {}; + let extra = {}; + for (let propName in refiners) { + if (propName in input) { + refined[propName] = refiners[propName](input[propName], propName); + } + } + for (let propName in input) { + if (!(propName in refiners)) { + extra[propName] = input[propName]; + } + } + return { refined, extra }; + } + function identity(raw) { + return raw; + } + function createFalsableFormatter(input) { + return input === false ? null : createFormatter(input); + } + + /* Date stuff that doesn't belong in datelib core + ----------------------------------------------------------------------------------------------------------------------*/ + // given a timed range, computes an all-day range that has the same exact duration, + // but whose start time is aligned with the start of the day. + function computeAlignedDayRange(timedRange) { + let dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1; + let start = startOfDay(timedRange.start); + let end = addDays(start, dayCnt); + return { start, end }; + } + // given a timed range, computes an all-day range based on how for the end date bleeds into the next day + // TODO: give nextDayThreshold a default arg + function computeVisibleDayRange(timedRange, nextDayThreshold = createDuration(0)) { + let startDay = null; + let endDay = null; + if (timedRange.end) { + endDay = startOfDay(timedRange.end); + let endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay` + // If the end time is actually inclusively part of the next day and is equal to or + // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`. + // Otherwise, leaving it as inclusive will cause it to exclude `endDay`. + if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) { + endDay = addDays(endDay, 1); + } + } + if (timedRange.start) { + startDay = startOfDay(timedRange.start); // the beginning of the day the range starts + // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day. + if (endDay && endDay <= startDay) { + endDay = addDays(startDay, 1); + } + } + return { start: startDay, end: endDay }; + } + function diffDates(date0, date1, dateEnv, largeUnit) { + if (largeUnit === 'year') { + return createDuration(dateEnv.diffWholeYears(date0, date1), 'year'); + } + if (largeUnit === 'month') { + return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month'); + } + return diffDayAndTime(date0, date1); // returns a duration + } + + function createEventInstance(defId, range) { + return { + instanceId: guid(), + defId, + range, + }; + } + + function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) { + for (let i = 0; i < recurringTypes.length; i += 1) { + let parsed = recurringTypes[i].parse(refined, dateEnv); + if (parsed) { + let { allDay } = refined; + if (allDay == null) { + allDay = defaultAllDay; + if (allDay == null) { + allDay = parsed.allDayGuess; + if (allDay == null) { + allDay = false; + } + } + } + return { + allDay, + duration: parsed.duration, + typeData: parsed.typeData, + typeId: i, + }; + } + } + return null; + } + function expandRecurring(eventStore, framingRange, context) { + let { dateEnv, pluginHooks, options } = context; + let { defs, instances } = eventStore; + // remove existing recurring instances + // TODO: bad. always expand events as a second step + instances = filterHash(instances, (instance) => !defs[instance.defId].recurringDef); + for (let defId in defs) { + let def = defs[defId]; + if (def.recurringDef) { + let { duration } = def.recurringDef; + if (!duration) { + duration = def.allDay ? + options.defaultAllDayEventDuration : + options.defaultTimedEventDuration; + } + let starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes); + for (let start of starts) { + let instance = createEventInstance(defId, { + start, + end: dateEnv.add(start, duration), + }); + instances[instance.instanceId] = instance; + } + } + } + return { defs, instances }; + } + /* + Event MUST have a recurringDef + */ + function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) { + let typeDef = recurringTypes[eventDef.recurringDef.typeId]; + let markers = typeDef.expand(eventDef.recurringDef.typeData, { + start: dateEnv.subtract(framingRange.start, duration), // for when event starts before framing range and goes into + end: framingRange.end, + }, dateEnv); + // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to + if (eventDef.allDay) { + markers = markers.map(startOfDay); + } + return markers; + } + + function parseEvents(rawEvents, eventSource, context, allowOpenRange, defIdMap, instanceIdMap) { + let eventStore = createEmptyEventStore(); + let eventRefiners = buildEventRefiners(context); + for (let rawEvent of rawEvents) { + let tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners, defIdMap, instanceIdMap); + if (tuple) { + eventTupleToStore(tuple, eventStore); + } + } + return eventStore; + } + function eventTupleToStore(tuple, eventStore = createEmptyEventStore()) { + eventStore.defs[tuple.def.defId] = tuple.def; + if (tuple.instance) { + eventStore.instances[tuple.instance.instanceId] = tuple.instance; + } + return eventStore; + } + // retrieves events that have the same groupId as the instance specified by `instanceId` + // or they are the same as the instance. + // why might instanceId not be in the store? an event from another calendar? + function getRelevantEvents(eventStore, instanceId) { + let instance = eventStore.instances[instanceId]; + if (instance) { + let def = eventStore.defs[instance.defId]; + // get events/instances with same group + let newStore = filterEventStoreDefs(eventStore, (lookDef) => isEventDefsGrouped(def, lookDef)); + // add the original + // TODO: wish we could use eventTupleToStore or something like it + newStore.defs[def.defId] = def; + newStore.instances[instance.instanceId] = instance; + return newStore; + } + return createEmptyEventStore(); + } + function isEventDefsGrouped(def0, def1) { + return Boolean(def0.groupId && def0.groupId === def1.groupId); + } + function createEmptyEventStore() { + return { defs: {}, instances: {} }; + } + function mergeEventStores(store0, store1) { + return { + defs: { ...store0.defs, ...store1.defs }, + instances: { ...store0.instances, ...store1.instances }, + }; + } + function filterEventStoreDefs(eventStore, filterFunc) { + let defs = filterHash(eventStore.defs, filterFunc); + let instances = filterHash(eventStore.instances, (instance) => (defs[instance.defId] // still exists? + )); + return { defs, instances }; + } + function excludeSubEventStore(master, sub) { + let { defs, instances } = master; + let filteredDefs = {}; + let filteredInstances = {}; + for (let defId in defs) { + if (!sub.defs[defId]) { // not explicitly excluded + filteredDefs[defId] = defs[defId]; + } + } + for (let instanceId in instances) { + if (!sub.instances[instanceId] && // not explicitly excluded + filteredDefs[instances[instanceId].defId] // def wasn't filtered away + ) { + filteredInstances[instanceId] = instances[instanceId]; + } + } + return { + defs: filteredDefs, + instances: filteredInstances, + }; + } + + function normalizeConstraint(input, context) { + if (Array.isArray(input)) { + return parseEvents(input, null, context, true); // allowOpenRange=true + } + if (typeof input === 'object' && input) { // non-null object + return parseEvents([input], null, context, true); // allowOpenRange=true + } + if (input != null) { + return String(input); + } + return null; + } + + // TODO: better called "EventSettings" or "EventConfig" + // TODO: move this file into structs + // TODO: separate constraint/overlap/allow, because selection uses only that, not other props + const EVENT_UI_REFINERS = { + display: String, + editable: Boolean, + startEditable: Boolean, + durationEditable: Boolean, + constraint: identity, // Identity, // circular reference. ts dies. event->constraint->event + overlap: identity, + allow: identity, + class: refineClassName, + className: refineClassName, + color: String, + contrastColor: String, + }; + const EMPTY_EVENT_UI = { + display: null, + startEditable: null, + durationEditable: null, + constraints: [], + overlap: null, + allows: [], + color: '', + contrastColor: '', + className: '', + }; + function createEventUi(refined, context) { + let constraint = normalizeConstraint(refined.constraint, context); + return { + display: refined.display || null, + startEditable: refined.startEditable != null ? refined.startEditable : refined.editable, + durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable, + constraints: constraint != null ? [constraint] : [], + overlap: refined.overlap != null ? refined.overlap : null, + allows: refined.allow != null ? [refined.allow] : [], + color: refined.color || '', + contrastColor: refined.contrastColor || '', + className: (refined.class ?? refined.className) || '', + }; + } + // TODO: prevent against problems with <2 args! + function combineEventUis(uis) { + return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI); + } + function combineTwoEventUis(item0, item1) { + return { + display: item1.display != null ? item1.display : item0.display, + startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable, + durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable, + constraints: item0.constraints.concat(item1.constraints), + overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap, + allows: item0.allows.concat(item1.allows), + color: item1.color || item0.color, + contrastColor: item1.contrastColor || item0.contrastColor, + className: joinClassNames(item0.className, item1.className), + }; + } + + const EVENT_NON_DATE_REFINERS = { + id: String, + groupId: String, + title: String, + url: String, + interactive: Boolean, + }; + const EVENT_DATE_REFINERS = { + start: identity, + end: identity, + date: identity, + allDay: Boolean, + }; + const EVENT_REFINERS = { + ...EVENT_NON_DATE_REFINERS, + ...EVENT_DATE_REFINERS, + extendedProps: identity, + }; + function parseEvent(raw, eventSource, context, allowOpenRange, refiners = buildEventRefiners(context), defIdMap, instanceIdMap) { + let { refined, extra } = refineEventDef(raw, context, refiners); + let defaultAllDay = computeIsDefaultAllDay(eventSource, context); + let recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes); + if (recurringRes) { + let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context, defIdMap); + def.recurringDef = { + typeId: recurringRes.typeId, + typeData: recurringRes.typeData, + duration: recurringRes.duration, + }; + return { def, instance: null }; + } + let singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange); + if (singleRes) { + let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context, defIdMap); + let instance = createEventInstance(def.defId, singleRes.range); + if (instanceIdMap && def.publicId && instanceIdMap[def.publicId]) { + instance.instanceId = instanceIdMap[def.publicId]; + } + return { def, instance }; + } + return null; + } + function refineEventDef(raw, context, refiners = buildEventRefiners(context)) { + return refineProps(raw, refiners); + } + function buildEventRefiners(context) { + return { ...EVENT_UI_REFINERS, ...EVENT_REFINERS, ...context.pluginHooks.eventRefiners }; + } + /* + Will NOT populate extendedProps with the leftover properties. + Will NOT populate date-related props. + */ + function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context, defIdMap) { + let def = { + title: refined.title || '', + groupId: refined.groupId || '', + publicId: refined.id || '', + url: refined.url || '', + recurringDef: null, + defId: ((defIdMap && refined.id) ? defIdMap[refined.id] : '') || guid(), + sourceId, + allDay, + hasEnd, + interactive: refined.interactive, + ui: createEventUi(refined, context), + extendedProps: { + ...(refined.extendedProps || {}), + ...extra, + }, + }; + for (let memberAdder of context.pluginHooks.eventDefMemberAdders) { + Object.assign(def, memberAdder(refined)); + } + // help out EventImpl from having user modify props + Object.freeze(def.ui.className); // might be simple string, but freeze still works + Object.freeze(def.extendedProps); + return def; + } + function parseSingle(refined, defaultAllDay, context, allowOpenRange) { + let { allDay } = refined; + let startMeta; + let startMarker = null; + let hasEnd = false; + let endMeta; + let endMarker = null; + let startInput = refined.start != null ? refined.start : refined.date; + startMeta = context.dateEnv.createMarkerMeta(startInput); + if (startMeta) { + startMarker = startMeta.marker; + } + else if (!allowOpenRange) { + return null; + } + if (refined.end != null) { + endMeta = context.dateEnv.createMarkerMeta(refined.end); + } + if (allDay == null) { + if (defaultAllDay != null) { + allDay = defaultAllDay; + } + else { + // fall back to the date props LAST + allDay = (!startMeta || startMeta.isTimeUnspecified) && + (!endMeta || endMeta.isTimeUnspecified); + } + } + if (allDay && startMarker) { + startMarker = startOfDay(startMarker); + } + if (endMeta) { + endMarker = endMeta.marker; + if (allDay) { + endMarker = startOfDay(endMarker); + } + if (startMarker && endMarker <= startMarker) { + endMarker = null; + } + } + if (endMarker) { + hasEnd = true; + } + else if (!allowOpenRange) { + hasEnd = context.options.forceEventDuration || false; + endMarker = context.dateEnv.add(startMarker, allDay ? + context.options.defaultAllDayEventDuration : + context.options.defaultTimedEventDuration); + } + return { + allDay, + hasEnd, + range: { start: startMarker, end: endMarker }, + }; + } + function computeIsDefaultAllDay(eventSource, context) { + let res = null; + if (eventSource) { + res = eventSource.defaultAllDay; + } + if (res == null) { + res = context.options.defaultAllDay; + } + return res; + } + + const STANDARD_PROPS = { + start: identity, + end: identity, + allDay: Boolean, + }; + function parseDateSpan(raw, dateEnv, defaultDuration) { + let span = parseOpenDateSpan(raw, dateEnv); + let { range } = span; + if (!range.start) { + return null; + } + if (!range.end) { + if (defaultDuration == null) { + return null; + } + range.end = dateEnv.add(range.start, defaultDuration); + } + return span; + } + /* + TODO: somehow combine with parseRange? + Will return null if the start/end props were present but parsed invalidly. + */ + function parseOpenDateSpan(raw, dateEnv) { + let { refined: standardProps, extra } = refineProps(raw, STANDARD_PROPS); + let startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null; + let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null; + let { allDay } = standardProps; + if (allDay == null) { + allDay = (startMeta && startMeta.isTimeUnspecified) && + (!endMeta || endMeta.isTimeUnspecified); + } + return { + range: { + start: startMeta ? startMeta.marker : null, + end: endMeta ? endMeta.marker : null, + }, + allDay, + ...extra, + }; + } + function isDateSpansEqual(span0, span1) { + return rangesEqual(span0.range, span1.range) && + span0.allDay === span1.allDay && + isSpanPropsEqual(span0, span1); + } + // the NON-DATE-RELATED props + function isSpanPropsEqual(span0, span1) { + for (let propName in span1) { + if (propName !== 'range' && propName !== 'allDay') { + if (span0[propName] !== span1[propName]) { + return false; + } + } + } + // are there any props that span0 has that span1 DOESN'T have? + // both have range/allDay, so no need to special-case. + for (let propName in span0) { + if (!(propName in span1)) { + return false; + } + } + return true; + } + function buildDateSpanApi(span, dateEnv) { + return { + ...buildRangeApi(span.range, dateEnv, span.allDay), + allDay: span.allDay, + }; + } + function buildRangeApiWithTimeZone(range, dateEnv, omitTime) { + return { + ...buildRangeApi(range, dateEnv, omitTime), + timeZone: dateEnv.timeZone, + }; + } + function buildRangeApi(range, dateEnv, omitTime) { + return { + start: dateEnv.toDate(range.start), + end: dateEnv.toDate(range.end), + startStr: dateEnv.formatIso(range.start, { omitTime }), + endStr: dateEnv.formatIso(range.end, { omitTime }), + }; + } + function fabricateEventRange(dateSpan, eventUiBases, context) { + let res = refineEventDef({ editable: false }, context); + let def = parseEventDef(res.refined, res.extra, '', // sourceId + dateSpan.allDay, true, // hasEnd + context); + return { + def, + ui: compileEventUi(def, eventUiBases), + instance: createEventInstance(def.defId, dateSpan.range), + range: dateSpan.range, + isStart: true, + isEnd: true, + }; + } + + function triggerDateSelect(selection, pev, context) { + context.emitter.trigger('select', { + ...buildDateSpanApiWithContext(selection, context), + jsEvent: pev ? pev.origEvent : null, // Is this always a mouse event? See #4655 + view: context.viewApi || context.calendarApi.view, + }); + } + function triggerDateUnselect(pev, context) { + context.emitter.trigger('unselect', { + jsEvent: pev ? pev.origEvent : null, // Is this always a mouse event? See #4655 + view: context.viewApi || context.calendarApi.view, + }); + } + function buildDateSpanApiWithContext(dateSpan, context) { + let props = {}; + for (let transform of context.pluginHooks.dateSpanTransforms) { + Object.assign(props, transform(dateSpan, context)); + } + Object.assign(props, buildDateSpanApi(dateSpan, context.dateEnv)); + return props; + } + // Given an event's allDay status and start date, return what its fallback end date should be. + // TODO: rename to computeDefaultEventEnd + function getDefaultEventEnd(allDay, marker, context) { + let { dateEnv, options } = context; + let end = marker; + if (allDay) { + end = startOfDay(end); + end = dateEnv.add(end, options.defaultAllDayEventDuration); + } + else { + end = dateEnv.add(end, options.defaultTimedEventDuration); + } + return end; + } + + // applies the mutation to ALL defs/instances within the event store + function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) { + let eventConfigs = compileEventUis(eventStore.defs, eventConfigBase); + let dest = createEmptyEventStore(); + for (let defId in eventStore.defs) { + let def = eventStore.defs[defId]; + dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context); + } + for (let instanceId in eventStore.instances) { + let instance = eventStore.instances[instanceId]; + let def = dest.defs[instance.defId]; // important to grab the newly modified def + dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context); + } + return dest; + } + function applyMutationToEventDef(eventDef, eventConfig, mutation, context) { + let standardProps = mutation.standardProps || {}; + // if hasEnd has not been specified, guess a good value based on deltas. + // if duration will change, there's no way the default duration will persist, + // and thus, we need to mark the event as having a real end + if (standardProps.hasEnd == null && + eventConfig.durationEditable && + (mutation.startDelta || mutation.endDelta)) { + standardProps.hasEnd = true; // TODO: is this mutation okay? + } + let copy = { + ...eventDef, + ...standardProps, + ui: { ...eventDef.ui, ...standardProps.ui }, // the only prop we want to recursively overlay + }; + if (mutation.extendedProps) { + copy.extendedProps = { ...copy.extendedProps, ...mutation.extendedProps }; + } + for (let applier of context.pluginHooks.eventDefMutationAppliers) { + applier(copy, mutation, context); + } + if (!copy.hasEnd && context.options.forceEventDuration) { + copy.hasEnd = true; + } + return copy; + } + function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef + eventConfig, mutation, context) { + let { dateEnv } = context; + let forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true; + let clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false; + let copy = { ...eventInstance }; + if (forceAllDay) { + copy.range = computeAlignedDayRange(copy.range); + } + if (mutation.datesDelta && eventConfig.startEditable) { + copy.range = { + start: dateEnv.add(copy.range.start, mutation.datesDelta), + end: dateEnv.add(copy.range.end, mutation.datesDelta), + }; + } + if (mutation.startDelta && eventConfig.durationEditable) { + copy.range = { + start: dateEnv.add(copy.range.start, mutation.startDelta), + end: copy.range.end, + }; + } + if (mutation.endDelta && eventConfig.durationEditable) { + copy.range = { + start: copy.range.start, + end: dateEnv.add(copy.range.end, mutation.endDelta), + }; + } + if (clearEnd) { + copy.range = { + start: copy.range.start, + end: getDefaultEventEnd(eventDef.allDay, copy.range.start, context), + }; + } + // in case event was all-day but the supplied deltas were not + // better util for this? + if (eventDef.allDay) { + copy.range = { + start: startOfDay(copy.range.start), + end: startOfDay(copy.range.end), + }; + } + // handle invalid durations + if (copy.range.end < copy.range.start) { + copy.range.end = getDefaultEventEnd(eventDef.allDay, copy.range.start, context); + } + return copy; + } + + class EventSourceImpl { + constructor(context, internalEventSource) { + this.context = context; + this.internalEventSource = internalEventSource; + } + remove() { + this.context.dispatch({ + type: 'REMOVE_EVENT_SOURCE', + sourceId: this.internalEventSource.sourceId, + }); + } + refetch() { + this.context.dispatch({ + type: 'FETCH_EVENT_SOURCES', + sourceIds: [this.internalEventSource.sourceId], + isRefetch: true, + }); + } + get id() { + return this.internalEventSource.publicId; + } + get url() { + return this.internalEventSource.meta.url; + } + get format() { + return this.internalEventSource.meta.format; // TODO: bad. not guaranteed + } + } + + class EventImpl { + // instance will be null if expressing a recurring event that has no current instances, + // OR if trying to validate an incoming external event that has no dates assigned + constructor(context, def, instance) { + this._context = context; + this._def = def; + this._instance = instance || null; + } + /* + TODO: make event struct more responsible for this + */ + setProp(name, val) { + if (name in EVENT_DATE_REFINERS) { + warn(`Cannot set date-related event property \`${name}\`. Use a method instead.`); + // TODO: make proper aliasing system? + } + else if (name === 'id') { + val = EVENT_NON_DATE_REFINERS[name](val); + this.mutate({ + standardProps: { publicId: val }, // hardcoded internal name + }); + } + else if (name in EVENT_NON_DATE_REFINERS) { + val = EVENT_NON_DATE_REFINERS[name](val); + this.mutate({ + standardProps: { [name]: val }, + }); + } + else if (name in EVENT_UI_REFINERS) { + let ui = EVENT_UI_REFINERS[name](val); + if (name === 'editable') { + ui = { startEditable: val, durationEditable: val }; + } + else { + ui = { [name]: val }; + } + this.mutate({ + standardProps: { ui }, + }); + } + else { + warn(`Cannot set event property \`${name}\`. Use setExtendedProp instead.`); + } + } + setExtendedProp(name, val) { + this.mutate({ + extendedProps: { [name]: val }, + }); + } + setStart(startInput, options = {}) { + let { dateEnv } = this._context; + let start = dateEnv.createMarker(startInput); + if (start && this._instance) { // TODO: warning if parsed bad + let instanceRange = this._instance.range; + let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); // what if parsed bad!? + if (options.maintainDuration) { + this.mutate({ datesDelta: startDelta }); + } + else { + this.mutate({ startDelta }); + } + } + } + setEnd(endInput, options = {}) { + let { dateEnv } = this._context; + let end; + if (endInput != null) { + end = dateEnv.createMarker(endInput); + if (!end) { + return; // TODO: warning if parsed bad + } + } + if (this._instance) { + if (end) { + let endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity); + this.mutate({ endDelta }); + } + else { + this.mutate({ standardProps: { hasEnd: false } }); + } + } + } + setDates(startInput, endInput, options = {}) { + let { dateEnv } = this._context; + let standardProps = { allDay: options.allDay }; + let start = dateEnv.createMarker(startInput); + let end; + if (!start) { + return; // TODO: warning if parsed bad + } + if (endInput != null) { + end = dateEnv.createMarker(endInput); + if (!end) { // TODO: warning if parsed bad + return; + } + } + if (this._instance) { + let instanceRange = this._instance.range; + // when computing the diff for an event being converted to all-day, + // compute diff off of the all-day values the way event-mutation does. + if (options.allDay === true) { + instanceRange = computeAlignedDayRange(instanceRange); + } + let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); + if (end) { + let endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity); + if (durationsEqual(startDelta, endDelta)) { + this.mutate({ datesDelta: startDelta, standardProps }); + } + else { + this.mutate({ startDelta, endDelta, standardProps }); + } + } + else { // means "clear the end" + standardProps.hasEnd = false; + this.mutate({ datesDelta: startDelta, standardProps }); + } + } + } + moveStart(deltaInput) { + let delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ startDelta: delta }); + } + } + moveEnd(deltaInput) { + let delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ endDelta: delta }); + } + } + moveDates(deltaInput) { + let delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ datesDelta: delta }); + } + } + setAllDay(allDay, options = {}) { + let standardProps = { allDay }; + let { maintainDuration } = options; + if (maintainDuration == null) { + maintainDuration = this._context.options.allDayMaintainDuration; + } + if (this._def.allDay !== allDay) { + standardProps.hasEnd = maintainDuration; + } + this.mutate({ standardProps }); + } + formatRange(formatInput) { + let { dateEnv } = this._context; + let instance = this._instance; + let formatter = createFormatter(formatInput); + if (this._def.hasEnd) { + return joinDateTimeFormatParts(dateEnv.formatRangeToParts(instance.range.start, instance.range.end, formatter)); + } + return joinDateTimeFormatParts(dateEnv.formatToParts(instance.range.start, formatter)); + } + mutate(mutation) { + let instance = this._instance; + if (instance) { + let def = this._def; + let context = this._context; + let { eventStore } = context.getCurrentData(); + let relevantEvents = getRelevantEvents(eventStore, instance.instanceId); + let eventConfigBase = { + '': { + display: '', + startEditable: true, + durationEditable: true, + constraints: [], + overlap: null, + allows: [], + color: '', + contrastColor: '', + className: '', + }, + }; + relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context); + let oldEvent = new EventImpl(context, def, instance); // snapshot + this._def = relevantEvents.defs[def.defId]; + this._instance = relevantEvents.instances[instance.instanceId]; + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, + }); + context.emitter.trigger('eventChange', { + oldEvent, + event: this, + relatedEvents: buildEventApis(relevantEvents, context, instance), + revert() { + context.dispatch({ + type: 'RESET_EVENTS', + eventStore, // the ORIGINAL store + }); + }, + }); + } + } + remove() { + let context = this._context; + let asStore = eventApiToStore(this); + context.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: asStore, + }); + context.emitter.trigger('eventRemove', { + event: this, + relatedEvents: [], + revert() { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: asStore, + }); + }, + }); + } + get source() { + let { sourceId } = this._def; + if (sourceId) { + return new EventSourceImpl(this._context, this._context.getCurrentData().eventSources[sourceId]); + } + return null; + } + get start() { + return this._instance ? + this._context.dateEnv.toDate(this._instance.range.start) : + null; + } + get end() { + return (this._instance && this._def.hasEnd) ? + this._context.dateEnv.toDate(this._instance.range.end) : + null; + } + get startStr() { + let instance = this._instance; + if (instance) { + return this._context.dateEnv.formatIso(instance.range.start, { + omitTime: this._def.allDay, + }); + } + return ''; + } + get endStr() { + let instance = this._instance; + if (instance && this._def.hasEnd) { + return this._context.dateEnv.formatIso(instance.range.end, { + omitTime: this._def.allDay, + }); + } + return ''; + } + // computable props that all access the def + // TODO: find a TypeScript-compatible way to do this at scale + get id() { return this._def.publicId; } + get groupId() { return this._def.groupId; } + get allDay() { return this._def.allDay; } + get title() { return this._def.title; } + get url() { return this._def.url; } + get display() { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier + get startEditable() { return this._def.ui.startEditable; } + get durationEditable() { return this._def.ui.durationEditable; } + get constraint() { return this._def.ui.constraints[0] || null; } + get overlap() { return this._def.ui.overlap; } + get allow() { return this._def.ui.allows[0] || null; } + get color() { return this._def.ui.color; } + get contrastColor() { return this._def.ui.contrastColor; } + // NOTE: user can't modify these because Object.freeze was called in event-def parsing + get className() { return this._def.ui.className; } + get extendedProps() { return this._def.extendedProps; } + toPlainObject(settings = {}) { + let def = this._def; + let { ui } = def; + let { startStr, endStr } = this; + let res = { + allDay: def.allDay, + }; + if (def.title) { + res.title = def.title; + } + if (startStr) { + res.start = startStr; + } + if (endStr) { + res.end = endStr; + } + if (def.publicId) { + res.id = def.publicId; + } + if (def.groupId) { + res.groupId = def.groupId; + } + if (def.url) { + res.url = def.url; + } + if (ui.display && ui.display !== 'auto') { + res.display = ui.display; + } + // TODO: what about recurring-event properties??? + // TODO: include startEditable/durationEditable/constraint/overlap/allow + if (ui.color) { + res.color = ui.color; + } + if (ui.contrastColor) { + res.contrastColor = ui.contrastColor; + } + if (ui.className) { + res.className = ui.className; + } + if (Object.keys(def.extendedProps).length) { + if (settings.collapseExtendedProps) { + Object.assign(res, def.extendedProps); + } + else { + res.extendedProps = def.extendedProps; + } + } + return res; + } + toJSON() { + return this.toPlainObject(); + } + } + function eventApiToStore(eventApi) { + let def = eventApi._def; + let instance = eventApi._instance; + return { + defs: { [def.defId]: def }, + instances: instance + ? { [instance.instanceId]: instance } + : {}, + }; + } + function buildEventApis(eventStore, context, excludeInstance) { + let { defs, instances } = eventStore; + let eventApis = []; + let excludeInstanceId = excludeInstance ? excludeInstance.instanceId : ''; + for (let id in instances) { + let instance = instances[id]; + let def = defs[instance.defId]; + if (instance.instanceId !== excludeInstanceId) { + eventApis.push(new EventImpl(context, def, instance)); + } + } + return eventApis; + } + + function getEventKey(seg) { + return seg.eventRange.instance.instanceId; + } + /* + Specifying nextDayThreshold signals that all-day ranges should be sliced. + */ + function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) { + let inverseBgByGroupId = {}; + let inverseBgByDefId = {}; + let defByGroupId = {}; + let bgRanges = []; + let fgRanges = []; + let eventUis = compileEventUis(eventStore.defs, eventUiBases); + for (let defId in eventStore.defs) { + let def = eventStore.defs[defId]; + let ui = eventUis[def.defId]; + if (ui.display === 'inverse-background') { + if (def.groupId) { + inverseBgByGroupId[def.groupId] = []; + if (!defByGroupId[def.groupId]) { + defByGroupId[def.groupId] = def; + } + } + else { + inverseBgByDefId[defId] = []; + } + } + } + for (let instanceId in eventStore.instances) { + let instance = eventStore.instances[instanceId]; + let def = eventStore.defs[instance.defId]; + let ui = eventUis[def.defId]; + let origRange = instance.range; + let normalRange = (!def.allDay && nextDayThreshold) ? + computeVisibleDayRange(origRange, nextDayThreshold) : + origRange; + let slicedRange = intersectRanges(normalRange, framingRange); + if (slicedRange) { + if (ui.display === 'inverse-background') { + if (def.groupId) { + inverseBgByGroupId[def.groupId].push(slicedRange); + } + else { + inverseBgByDefId[instance.defId].push(slicedRange); + } + } + else if (ui.display !== 'none') { + (ui.display === 'background' ? bgRanges : fgRanges).push({ + def, + ui, + instance, + range: slicedRange, + isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(), + isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(), + }); + } + } + } + for (let groupId in inverseBgByGroupId) { // BY GROUP + let ranges = inverseBgByGroupId[groupId]; + let invertedRanges = invertRanges(ranges, framingRange); + for (let invertedRange of invertedRanges) { + let def = defByGroupId[groupId]; + let ui = eventUis[def.defId]; + bgRanges.push({ + def, + ui, + instance: null, + range: invertedRange, + isStart: false, + isEnd: false, + }); + } + } + for (let defId in inverseBgByDefId) { + let ranges = inverseBgByDefId[defId]; + let invertedRanges = invertRanges(ranges, framingRange); + for (let invertedRange of invertedRanges) { + bgRanges.push({ + def: eventStore.defs[defId], + ui: eventUis[defId], + instance: null, + range: invertedRange, + isStart: false, + isEnd: false, + }); + } + } + return { bg: bgRanges, fg: fgRanges }; + } + function hasBgRendering(def) { + return def.ui.display === 'background' || def.ui.display === 'inverse-background'; + } + function setElEventRange(el, eventRange) { + el.fcEventRange = eventRange; + } + function getElEventRange(el) { + return el.fcEventRange || + el.parentNode.fcEventRange || // for the harness + null; + } + // event ui computation + function compileEventUis(eventDefs, eventUiBases) { + return mapHash(eventDefs, (eventDef) => compileEventUi(eventDef, eventUiBases)); + } + /* + I wish we didn't need to deal with inheritance of all properties all together + I wish you could resolve just eventDisplay first, then the others + */ + function compileEventUi(eventDef, eventUiBases) { + const uis = []; + const fallbackBase = eventUiBases['']; + const defBase = eventUiBases[eventDef.defId]; + if (fallbackBase) { + uis.push(fallbackBase); + } + if (defBase) { + uis.push(defBase); + } + uis.push(eventDef.ui); + return combineEventUis(uis); + } + function sortEventSegs(segs, eventOrderSpecs) { + let objs = segs.map(buildSegCompareObj); + objs.sort((obj0, obj1) => compareByFieldSpecs(obj0, obj1, eventOrderSpecs)); // !!! + return objs.map((c) => c._seg); + } + // returns a object with all primitive props that can be compared + function buildSegCompareObj(seg) { + let { eventRange } = seg; + let eventDef = eventRange.def; + let range = eventRange.instance ? eventRange.instance.range : eventRange.range; + let start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events + let end = range.end ? range.end.valueOf() : 0; // " + return { + ...eventDef.extendedProps, + ...eventDef, + id: eventDef.publicId, + start, + end, + duration: end - start, + allDay: Number(eventDef.allDay), + _seg: seg, // for later retrieval + }; + } + function computeEventRangeDraggable(eventRange, context) { + let { pluginHooks } = context; + let transformers = pluginHooks.isDraggableTransformers; + let { def, ui } = eventRange; + let val = ui.startEditable; + for (let transformer of transformers) { + val = transformer(val, def, ui, context); + } + return val; + } + /* + slicedStart/slicedEnd are optionally supplied to signal where breaks occur in view-specific segment + a better approach is to always slice with dates and always supply this argument, + however, daygrid only slices by row/col + */ + function buildEventRangeTimeText(timeFormat, eventRange, // timed/whole-day span + slicedStart, // view-sliced timed/whole-day span + slicedEnd, // view-sliced timed/whole-day span + isStart, isEnd, context, defaultDisplayEventTime = true, defaultDisplayEventEnd = true) { + const { dateEnv, options } = context; + const { def } = eventRange; + let { displayEventTime, displayEventEnd } = options; + if (displayEventTime == null) { + displayEventTime = defaultDisplayEventTime !== false; + } + if (displayEventEnd == null) { + displayEventEnd = defaultDisplayEventEnd !== false; + } + const startDate = (!isStart && + slicedStart && + // if seg is the first seg, but start-date cut-off by slotMinTime, (technically isStart=false) + // we still want to display the original start-time + startOfDay(slicedStart).valueOf() !== startOfDay(eventRange.instance.range.start).valueOf()) + ? slicedStart + : eventRange.instance.range.start; + const endDate = (!isEnd && + slicedEnd && + // See above HACK, but for end-time + startOfDay(addMs(slicedEnd, -1)).valueOf() !== startOfDay(addMs(eventRange.instance.range.end, -1)).valueOf()) + ? slicedEnd + : eventRange.instance.range.end; + if (displayEventTime && !def.allDay) { + if (displayEventEnd && (isStart || isEnd) && def.hasEnd) { + // TODO: put this functionality in @full-ui/headless-calendar ? + // NOTE: produces strings like '12:00pm - 1:00pm', without condensing dayPeriod, + // but that's okay since it's technically a different dayPeriod on a different day + const rangeParts = dateEnv.formatRangeToParts(startDate, endDate, timeFormat); + const multiDaySeparator = detectMultiDayTimes(rangeParts); + // + if (multiDaySeparator != null) { + return joinDateTimeFormatParts(dateEnv.formatToParts(startDate, timeFormat)) + + multiDaySeparator + + joinDateTimeFormatParts(dateEnv.formatToParts(endDate, timeFormat)); + } + return joinDateTimeFormatParts(rangeParts); + } + if (isStart) { + return joinDateTimeFormatParts(dateEnv.formatToParts(startDate, timeFormat)); + } + } + return ''; + } + const dateUnits = new Set(['year', 'month', 'day']); // TODO: DRY + function detectMultiDayTimes(parts) { + let sharedPart; + let hasDatePart = false; + for (const part of parts) { + if (part.source === 'shared') { + sharedPart = part; + } + if (dateUnits.has(part.type)) { + hasDatePart = true; + } + } + return hasDatePart ? sharedPart.value : undefined; + } + function getEventRangeMeta(eventRange, todayRange, nowDate) { + let segRange = eventRange.range; + return { + isPast: segRange.end <= (nowDate || todayRange.start), + isFuture: segRange.start >= (nowDate || todayRange.end), + isToday: todayRange && rangeContainsMarker(todayRange, segRange.start), + }; + } + function buildEventRangeKey(eventRange) { + return eventRange.instance + ? eventRange.instance.instanceId + : `${eventRange.def.defId}:${eventRange.range.start.toISOString()}`; + // inverse-background events don't have specific instances. TODO: better solution + } + function getEventTagAndAttrs(eventRange, context) { + let { def, instance } = eventRange; + let { url } = def; + if (url) { + return ['a', { href: url }, true]; + } + let { emitter, options } = context; + let { eventInteractive } = options; + if (eventInteractive == null) { + eventInteractive = def.interactive; + if (eventInteractive == null) { + eventInteractive = Boolean(emitter.hasHandlers('eventClick')); + } + } + let attrs; + // mock what happens in EventClicking + if (eventInteractive) { + // only attach keyboard-related handlers because click handler is already done in EventClicking + attrs = createAriaKeyboardAttrs((ev) => { + emitter.trigger('eventClick', { + el: ev.target, + event: new EventImpl(context, def, instance), + jsEvent: ev, + view: context.viewApi, + }); + }); + attrs = { role: 'button', ...attrs }; + } + return ['div', attrs, eventInteractive]; + } + + const classNamesRe = /(^c|C)lass(Name)?$/; + const contentRe = /Content$/; + const lifecycleRe = /(DidMount|WillUnmount)$/; + const handlerRe = /^on[A-Z]/; + // Somewhat tracks COMPLEX_OPTION_COMPARATORS + // Unfortunately always need 'maybe' to handle undefined inital value, because of CalendarDataManager + const customMergeFuncs = { + buttons: mergeMaybePropsDepth1, + }; + function mergeViewOptionsMap(...hashes) { + const merged = {}; + for (const hash of hashes) { + for (const viewName in hash) { + const viewOptions = hash[viewName]; + if (!merged[viewName]) { + merged[viewName] = viewOptions; + } + else { + merged[viewName] = mergeCalendarOptions(merged[viewName], viewOptions); + } + } + } + return merged; + } + /* + Merges an array of RAW options objects into a single object. + The second argument allows for an array of property names who's object values will be merged together. + */ + function mergeCalendarOptions(...optionSets) { + let dest = {}; + for (const options of optionSets) { + for (let name in options) { + if (name in dest) { + const mergeFunc = customMergeFuncs[name] || (classNamesRe.test(name) ? joinFuncishClassNames : + contentRe.test(name) ? mergeContentInjectors : + lifecycleRe.test(name) ? mergeLifecycleCallbacks : undefined); + dest[name] = mergeFunc + ? mergeFunc(dest[name], options[name], name) + : options[name]; // last wins + } + else { + dest[name] = options[name]; // last wins + } + } + } + return dest; + } + /* + Called while merging raw option objects, before the normal option refinement pass. + ClassName values are validated here because merging may join raw strings, or build a + combined function that joins raw generator outputs later. Without checking each part + before joinClassNames, invalid values like objects/arrays could be stringified into + valid-looking class strings before refineClassName/refineClassNameGenerator see them. + + Ideally this would be a single-pass responsibility: either merge after refinement, or + store unjoined class parts during raw merging and have one later refiner validate and + join all parts. For now, this merge helper validates just enough to avoid corrupting + invalid values before the formal refinement pass. + */ + function joinFuncishClassNames(input0, // added to string first + input1, optionName) { + const isFunc0 = typeof input0 === 'function'; + const isFunc1 = typeof input1 === 'function'; + if (isFunc0 || isFunc1) { + const combinedFunc = (info) => { + return joinClassNames(refineClassName(isFunc0 ? input0(info) : input0, optionName), refineClassName(isFunc1 ? input1(info) : input1, optionName)); + }; + combinedFunc.parts = [input0, input1]; // see CalendarDataManager::processRawCalendarOptions + return combinedFunc; + } + return joinClassNames(refineClassName(input0, optionName), refineClassName(input1, optionName)); + } + function mergeContentInjectors(contentGenerator0, // fallback + contentGenerator1) { + if (typeof contentGenerator1 === 'function') { + // fabricate new function + const combinedFunc = (renderProps) => { + const res = contentGenerator1(renderProps); + if (res === true) { // `true` indicates use-fallback + if (typeof contentGenerator0 === 'function') { + return contentGenerator0(renderProps); + } + return contentGenerator0; + } + return res; + }; + combinedFunc.parts = [contentGenerator0, contentGenerator1]; // see CalendarDataManager::processRawCalendarOptions + return combinedFunc; + } + if (contentGenerator1 != null) { + return contentGenerator1; + } + return contentGenerator0; + } + function mergeLifecycleCallbacks(fn0, // called first + fn1) { + if (fn0 && fn1) { + // fabricate new function + const combinedFunc = (...args) => { + fn0(...args); + fn1(...args); + }; + combinedFunc.parts = [fn0, fn1]; // see CalendarDataManager::processRawCalendarOptions + return combinedFunc; + } + return fn0 || fn1; + } + function isNonHandlerPropsEqual(obj0, obj1) { + const keys = getUnequalProps(obj0, obj1); + for (let key of keys) { + if (!handlerRe.test(key)) { + return false; + } + } + return true; + } + function isMergedPropsEqual(val0, val1) { + const parts0 = val0 && val0.parts; + const parts1 = val1 && val1.parts; + if (parts0 && parts1) { + const count0 = parts0.length; + const count1 = parts1.length; + if (count0 !== count1) { + return false; + } + for (let i = 0; i < count0; i++) { + if (!(parts0[i] === parts1[i] || isMergedPropsEqual(parts0[i], parts1[i]))) { + return false; + } + } + return true; + } + return false; + } + + const globalLocales = []; + + const MINIMAL_RAW_EN_LOCALE = { + code: 'en', + week: { + dow: 0, // Sunday is the first day of the week + doy: 4, // 4 days need to be within the year to be considered the first week + }, + direction: 'ltr', // TODO: make a real type for this + todayText: 'Today', + prevText: 'Prev', + nextText: 'Next', + prevYearText: 'Prev year', + nextYearText: 'Next year', + yearText: 'Year', + monthText: 'Month', + weekTextLong: 'Week', + dayText: 'Day', + listText: 'List', + closeHint: 'Close', + eventsHint: 'Events', + allDayText: 'All-day', + timedText: 'Timed', + moreLinkText: 'more', + noEventsText: 'No events to display', + }; + /* + Includes things we don't want other locales to inherit, + things that derive from other translatable strings. + */ + const RAW_EN_LOCALE = { + ...MINIMAL_RAW_EN_LOCALE, + // if a locale doesn't define this, fall back to weekTextLong, don't use EN + weekTextShort: 'W', + todayHint: (unitText, unit) => { + return (unit === 'day') + ? 'Today' + : `This ${unitText}`; + }, + prevHint: 'Previous $0', + nextHint: 'Next $0', + viewHint: '$0 view', + viewChangeHint: 'Change view', + navLinkHint: 'Go to $0', + moreLinkHint(eventCnt) { + return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`; + }, + }; + function organizeRawLocales(explicitRawLocales) { + let defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en'; + let allRawLocales = globalLocales.concat(explicitRawLocales); + let rawLocaleMap = { + en: RAW_EN_LOCALE, + }; + for (let rawLocale of allRawLocales) { + rawLocaleMap[rawLocale.code] = rawLocale; + } + return { + map: rawLocaleMap, + defaultCode, + }; + } + function buildLocale(inputSingular, available) { + if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) { + return parseLocale(inputSingular.code, [inputSingular.code], inputSingular); + } + return queryLocale(inputSingular, available); + } + function queryLocale(codeArg, available) { + let codes = [].concat(codeArg || []); // will convert to array + let raw = queryRawLocale(codes, available) || RAW_EN_LOCALE; + return parseLocale(codeArg, codes, raw); + } + function queryRawLocale(codes, available) { + for (let i = 0; i < codes.length; i += 1) { + let parts = codes[i].toLocaleLowerCase().split('-'); + for (let j = parts.length; j > 0; j -= 1) { + let simpleId = parts.slice(0, j).join('-'); + if (available[simpleId]) { + return available[simpleId]; + } + } + } + return null; + } + function parseLocale(codeArg, codes, raw) { + let merged = mergeCalendarOptions(MINIMAL_RAW_EN_LOCALE, raw); + delete merged.code; // don't want this part of the options + let { week } = merged; + delete merged.week; + return { + codeArg, + codes, + week, + simpleNumberFormat: new Intl.NumberFormat(codeArg), + options: merged, + }; + } + + class JsonRequestError extends Error { + constructor(message, response) { + super(message); + this.response = response; + } + } + function requestJson(method, url, params) { + method = method.toUpperCase(); + const fetchOptions = { + method, + }; + if (method === 'GET') { + url += (url.indexOf('?') === -1 ? '?' : '&') + + new URLSearchParams(params); + } + else { + fetchOptions.body = new URLSearchParams(params); + fetchOptions.headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return fetch(url, fetchOptions).then((fetchRes) => { + if (fetchRes.ok) { + return fetchRes.json().then((parsedResponse) => { + return [parsedResponse, fetchRes]; + }, () => { + throw new JsonRequestError('Failure parsing JSON', fetchRes); + }); + } + else { + throw new JsonRequestError('Request failed', fetchRes); + } + }); + } + + function handleDateProfile(dateProfile, context) { + context.emitter.trigger('datesSet', { + ...buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv), + view: context.viewApi, + }); + } + + function handleEventStore(eventStore, context) { + let { emitter } = context; + if (emitter.hasHandlers('eventsSet')) { + emitter.trigger('eventsSet', buildEventApis(eventStore, context)); + } + } + + let eventSourceDef$2 = { + ignoreRange: true, + parseMeta(refined) { + if (Array.isArray(refined.events)) { + return refined.events; + } + return null; + }, + fetch(arg, successCallback) { + successCallback({ + rawEvents: arg.eventSource.meta, + }); + }, + }; + const arrayEventSourcePlugin = { + name: 'array-event-source', + eventSourceDefs: [eventSourceDef$2], + }; + + /* + given a function that resolves a result asynchronously. + the function can either call passed-in success and failure callbacks, + or it can return a promise. + if you need to pass additional params to func, bind them first. + */ + function unpromisify(func, normalizedSuccessCallback, normalizedFailureCallback) { + // guard against success/failure callbacks being called more than once + // and guard against a promise AND callback being used together. + let isResolved = false; + let wrappedSuccess = function (res) { + if (!isResolved) { + isResolved = true; + normalizedSuccessCallback(res); + } + }; + let wrappedFailure = function (error) { + if (!isResolved) { + isResolved = true; + normalizedFailureCallback(error); + } + }; + let res = func(wrappedSuccess, wrappedFailure); + if (res && typeof res.then === 'function') { + res.then(wrappedSuccess, wrappedFailure); + } + } + + let eventSourceDef$1 = { + parseMeta(refined) { + if (typeof refined.events === 'function') { + return refined.events; + } + return null; + }, + fetch(arg, successCallback, errorCallback) { + const { dateEnv } = arg.context; + const func = arg.eventSource.meta; + unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), (rawEvents) => successCallback({ rawEvents }), errorCallback); + }, + }; + const funcEventSourcePlugin = { + name: 'func-event-source', + eventSourceDefs: [eventSourceDef$1], + }; + + const JSON_FEED_EVENT_SOURCE_REFINERS = { + method: String, + extraParams: identity, + startParam: String, + endParam: String, + timeZoneParam: String, + }; + + let eventSourceDef = { + parseMeta(refined) { + if (refined.url && (refined.format === 'json' || !refined.format)) { + return { + url: refined.url, + format: 'json', + method: (refined.method || 'GET').toUpperCase(), + extraParams: refined.extraParams, + startParam: refined.startParam, + endParam: refined.endParam, + timeZoneParam: refined.timeZoneParam, + }; + } + return null; + }, + fetch(arg, successCallback, errorCallback) { + const { meta } = arg.eventSource; + const requestParams = buildRequestParams(meta, arg.range, arg.context); + requestJson(meta.method, meta.url, requestParams).then(([rawEvents, response]) => { + successCallback({ rawEvents, response }); + }, errorCallback); + }, + }; + const jsonFeedEventSourcePlugin = { + name: 'json-event-source', + eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS, + eventSourceDefs: [eventSourceDef], + }; + function buildRequestParams(meta, range, context) { + let { dateEnv, options } = context; + let startParam; + let endParam; + let timeZoneParam; + let customRequestParams; + let params = {}; + startParam = meta.startParam; + if (startParam == null) { + startParam = options.startParam; + } + endParam = meta.endParam; + if (endParam == null) { + endParam = options.endParam; + } + timeZoneParam = meta.timeZoneParam; + if (timeZoneParam == null) { + timeZoneParam = options.timeZoneParam; + } + // retrieve any outbound GET/POST data from the options + if (typeof meta.extraParams === 'function') { + // supplied as a function that returns a key/value object + customRequestParams = meta.extraParams(); + } + else { + // probably supplied as a straight key/value object + customRequestParams = meta.extraParams || {}; + } + Object.assign(params, customRequestParams); + params[startParam] = dateEnv.formatIso(range.start); + params[endParam] = dateEnv.formatIso(range.end); + if (dateEnv.timeZone !== 'local') { + params[timeZoneParam] = dateEnv.timeZone; + } + return params; + } + + const changeHandlerPlugin = { + name: 'change-handler', + optionChangeHandlers: { + controller(controller, context) { + // TODO: the initial setting is in CalendarDataManager + controller._setApi(context.calendarApi); + }, + events(events, context) { + handleEventSources([events], context); + }, + eventSources: handleEventSources, + }, + }; + /* + BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out + */ + function handleEventSources(inputs, context) { + let unfoundSources = hashValuesToArray(context.getCurrentData().eventSources); + if (unfoundSources.length === 1 && + inputs.length === 1 && + Array.isArray(unfoundSources[0]._raw) && + Array.isArray(inputs[0])) { + context.dispatch({ + type: 'RESET_RAW_EVENTS', + sourceId: unfoundSources[0].sourceId, + rawEvents: inputs[0], + }); + return; + } + let newInputs = []; + for (let input of inputs) { + let inputFound = false; + for (let i = 0; i < unfoundSources.length; i += 1) { + if (unfoundSources[i]._raw === input) { + unfoundSources.splice(i, 1); // delete + inputFound = true; + break; + } + } + if (!inputFound) { + newInputs.push(input); + } + } + for (let unfoundSource of unfoundSources) { + context.dispatch({ + type: 'REMOVE_EVENT_SOURCE', + sourceId: unfoundSource.sourceId, + }); + } + for (let newInput of newInputs) { + context.calendarApi.addEventSource(newInput); + } + } + + const EVENT_SOURCE_REFINERS = { + id: String, + defaultAllDay: Boolean, + url: String, + format: String, + events: identity, // array or function + eventDataTransform: identity, + // for any network-related sources + success: identity, + failure: identity, + }; + function parseEventSource(raw, context, refiners = buildEventSourceRefiners(context)) { + let rawObj; + if (typeof raw === 'string') { + rawObj = { url: raw }; + } + else if (typeof raw === 'function' || Array.isArray(raw)) { + rawObj = { events: raw }; + } + else if (typeof raw === 'object' && raw) { // not null + rawObj = raw; + } + if (rawObj) { + let { refined, extra } = refineProps(rawObj, refiners); + let metaRes = buildEventSourceMeta(refined, context); + if (metaRes) { + return { + _raw: raw, + isFetching: false, + latestFetchId: '', + fetchRange: null, + defaultAllDay: refined.defaultAllDay, + eventDataTransform: refined.eventDataTransform, + success: refined.success, + failure: refined.failure, + publicId: refined.id || '', + sourceId: guid(), + sourceDefId: metaRes.sourceDefId, + meta: metaRes.meta, + ui: createEventUi(refined, context), + extendedProps: extra, + }; + } + } + return null; + } + function buildEventSourceRefiners(context) { + return { ...EVENT_UI_REFINERS, ...EVENT_SOURCE_REFINERS, ...context.pluginHooks.eventSourceRefiners }; + } + function buildEventSourceMeta(raw, context) { + let defs = context.pluginHooks.eventSourceDefs; + for (let i = defs.length - 1; i >= 0; i -= 1) { // later-added plugins take precedence + let def = defs[i]; + let meta = def.parseMeta(raw); + if (meta) { + return { sourceDefId: i, meta }; + } + } + return null; + } + + function initEventSources(calendarOptions, dateProfile, context) { + let activeRange = dateProfile ? dateProfile.activeRange : null; + return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context); + } + function reduceEventSources(eventSources, action, dateProfile, context) { + let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? + switch (action.type) { + case 'ADD_EVENT_SOURCES': // already parsed + return addSources(eventSources, action.sources, activeRange, context); + case 'REMOVE_EVENT_SOURCE': + return removeSource(eventSources, action.sourceId); + case 'PREV': // TODO: how do we track all actions that affect dateProfile :( + case 'NEXT': + case 'CHANGE_DATE': + case 'CHANGE_VIEW_TYPE': + if (dateProfile) { + return fetchDirtySources(eventSources, activeRange, context); + } + return eventSources; + case 'FETCH_EVENT_SOURCES': + return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type? + arrayToHash(action.sourceIds) : + excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context); + case 'RECEIVE_EVENTS': + case 'RECEIVE_EVENT_ERROR': + return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange); + case 'REMOVE_ALL_EVENT_SOURCES': + return {}; + default: + return eventSources; + } + } + function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) { + let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? + return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context); + } + function computeEventSourcesLoading(eventSources) { + for (let sourceId in eventSources) { + if (eventSources[sourceId].isFetching) { + return true; + } + } + return false; + } + function addSources(eventSourceHash, sources, fetchRange, context) { + let hash = {}; + for (let source of sources) { + hash[source.sourceId] = source; + } + if (fetchRange) { + hash = fetchDirtySources(hash, fetchRange, context); + } + return { ...eventSourceHash, ...hash }; + } + function removeSource(eventSourceHash, sourceId) { + return filterHash(eventSourceHash, (eventSource) => eventSource.sourceId !== sourceId); + } + function fetchDirtySources(sourceHash, fetchRange, context) { + return fetchSourcesByIds(sourceHash, filterHash(sourceHash, (eventSource) => isSourceDirty(eventSource, fetchRange, context)), fetchRange, false, context); + } + function isSourceDirty(eventSource, fetchRange, context) { + if (!doesSourceNeedRange(eventSource, context)) { + return !eventSource.latestFetchId; + } + return !context.options.lazyFetching || + !eventSource.fetchRange || + eventSource.isFetching || // always cancel outdated in-progress fetches + fetchRange.start < eventSource.fetchRange.start || + fetchRange.end > eventSource.fetchRange.end; + } + function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) { + let nextSources = {}; + for (let sourceId in prevSources) { + let source = prevSources[sourceId]; + if (sourceIdHash[sourceId]) { + nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context); + } + else { + nextSources[sourceId] = source; + } + } + return nextSources; + } + function fetchSource(eventSource, fetchRange, isRefetch, context) { + let { options, calendarApi } = context; + let sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId]; + let fetchId = guid(); + sourceDef.fetch({ + eventSource, + range: fetchRange, + isRefetch, + context, + }, (res) => { + let { rawEvents } = res; + if (options.eventSourceSuccess) { + rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.response) || rawEvents; + } + if (eventSource.success) { + rawEvents = eventSource.success.call(calendarApi, rawEvents, res.response) || rawEvents; + } + context.dispatch({ + type: 'RECEIVE_EVENTS', + sourceId: eventSource.sourceId, + fetchId, + fetchRange, + rawEvents, + }); + }, (error) => { + let errorHandled = false; + if (options.eventSourceFailure) { + options.eventSourceFailure.call(calendarApi, error); + errorHandled = true; + } + if (eventSource.failure) { + eventSource.failure(error); + errorHandled = true; + } + if (!errorHandled) { + warn(`Unhandled event source error: ${error.message}`, error); + } + context.dispatch({ + type: 'RECEIVE_EVENT_ERROR', + sourceId: eventSource.sourceId, + fetchId, + fetchRange, + error, + }); + }); + return { + ...eventSource, + isFetching: true, + latestFetchId: fetchId, + }; + } + function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) { + let eventSource = sourceHash[sourceId]; + if (eventSource && // not already removed + fetchId === eventSource.latestFetchId) { + return { + ...sourceHash, + [sourceId]: { + ...eventSource, + isFetching: false, + fetchRange, // also serves as a marker that at least one fetch has completed + }, + }; + } + return sourceHash; + } + function excludeStaticSources(eventSources, context) { + return filterHash(eventSources, (eventSource) => doesSourceNeedRange(eventSource, context)); + } + function parseInitialSources(rawOptions, context) { + let refiners = buildEventSourceRefiners(context); + let rawSources = [].concat(rawOptions.eventSources || []); + let sources = []; // parsed + if (rawOptions.initialEvents) { + rawSources.unshift(rawOptions.initialEvents); + } + if (rawOptions.events) { + rawSources.unshift(rawOptions.events); + } + for (let rawSource of rawSources) { + let source = parseEventSource(rawSource, context, refiners); + if (source) { + sources.push(source); + } + } + return sources; + } + function doesSourceNeedRange(eventSource, context) { + let defs = context.pluginHooks.eventSourceDefs; + return !defs[eventSource.sourceDefId].ignoreRange; + } + + const SIMPLE_RECURRING_REFINERS = { + daysOfWeek: identity, + startTime: createDuration, + endTime: createDuration, + duration: createDuration, + startRecur: identity, + endRecur: identity, + }; + + let recurring = { + parse(refined, dateEnv) { + if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) { + let recurringData = { + daysOfWeek: refined.daysOfWeek || null, + startTime: refined.startTime || null, + endTime: refined.endTime || null, + startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null, + endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null, + dateEnv, + }; + let duration; + if (refined.duration) { + duration = refined.duration; + } + if (!duration && refined.startTime && refined.endTime) { + duration = subtractDurations(refined.endTime, refined.startTime); + } + return { + allDayGuess: Boolean(!refined.startTime && !refined.endTime), + duration, + typeData: recurringData, // doesn't need endTime anymore but oh well + }; + } + return null; + }, + expand(typeData, framingRange, dateEnv) { + let clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur }); + if (clippedFramingRange) { + return expandRanges(typeData.daysOfWeek, typeData.startTime, typeData.dateEnv, dateEnv, clippedFramingRange); + } + return []; + }, + }; + const simpleRecurringEventsPlugin = { + name: 'simple-recurring-event', + recurringTypes: [recurring], + eventRefiners: SIMPLE_RECURRING_REFINERS, + }; + function expandRanges(daysOfWeek, startTime, eventDateEnv, calendarDateEnv, framingRange) { + let dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null; + let dayMarker = startOfDay(framingRange.start); + let endMarker = framingRange.end; + let instanceStarts = []; + // https://github.com/fullcalendar/fullcalendar/issues/7934 + if (startTime) { + if (startTime.milliseconds < 0) { + // possible for next-day to have negative business hours that go into current day + endMarker = addDays(endMarker, 1); + } + else if (startTime.milliseconds >= 1000 * 60 * 60 * 24) { + // possible for prev-day to have >24hr business hours that go into current day + dayMarker = addDays(dayMarker, -1); + } + } + while (dayMarker < endMarker) { + let instanceStart; + // if everyday, or this particular day-of-week + if (!dowHash || dowHash[dayMarker.getUTCDay()]) { + if (startTime) { + instanceStart = calendarDateEnv.add(dayMarker, startTime); + } + else { + instanceStart = dayMarker; + } + instanceStarts.push(calendarDateEnv.createMarker(eventDateEnv.toDate(instanceStart))); + } + dayMarker = addDays(dayMarker, 1); + } + return instanceStarts; + } + + /* + this array is exposed on the root namespace so that UMD plugins can add to it. + see the rollup-bundles script. + */ + const globalPlugins = [ + arrayEventSourcePlugin, + funcEventSourcePlugin, + jsonFeedEventSourcePlugin, + simpleRecurringEventsPlugin, + changeHandlerPlugin, + { + name: 'misc', + isLoadingFuncs: [ + (state) => computeEventSourcesLoading(state.eventSources), + ], + propSetHandlers: { + dateProfile: handleDateProfile, + eventStore: handleEventStore, + }, + }, + ]; + + var r,u,i,f=[],c=l$2,e=c.__b,a=c.__r,v=c.diffed,l=c.__c,m=c.unmount,s=c.__;function j$1(){for(var n;n=f.shift();){var t=n.__H;if(n.__P&&t)try{t.__h.some(z),t.__h.some(B$1),t.__h=[];}catch(r){t.__h=[],c.__e(r,n.__v);}}}c.__b=function(n){r=null,e&&e(n);},c.__=function(n,t){n&&t.__k&&t.__k.__m&&(n.__m=t.__k.__m),s&&s(n,t);},c.__r=function(n){a&&a(n);var i=(r=n.__c).__H;i&&(u===r?(i.__h=[],r.__h=[],i.__.some(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0;})):(i.__h.some(z),i.__h.some(B$1),i.__h=[],0)),u=r;},c.diffed=function(n){v&&v(n);var t=n.__c;t&&t.__H&&(t.__H.__h.length&&(1!==f.push(t)&&i===c.requestAnimationFrame||((i=c.requestAnimationFrame)||w)(j$1)),t.__H.__.some(function(n){n.u&&(n.__H=n.u),n.u=void 0;})),u=r=null;},c.__c=function(n,t){t.some(function(n){try{n.__h.some(z),n.__h=n.__h.filter(function(n){return !n.__||B$1(n)});}catch(r){t.some(function(n){n.__h&&(n.__h=[]);}),t=[],c.__e(r,n.__v);}}),l&&l(n,t);},c.unmount=function(n){m&&m(n);var t,r=n.__c;r&&r.__H&&(r.__H.__.some(function(n){try{z(n);}catch(n){t=n;}}),r.__H=void 0,t&&c.__e(t,r.__v));};var k="function"==typeof requestAnimationFrame;function w(n){var t,r=function(){clearTimeout(u),k&&cancelAnimationFrame(t),setTimeout(n);},u=setTimeout(r,35);k&&(t=requestAnimationFrame(r));}function z(n){var t=r,u=n.__c;"function"==typeof u&&(n.__c=void 0,u()),r=t;}function B$1(n){var t=r;n.__c=n.__(),r=t;} + + function g(n,t){for(var e in t)n[e]=t[e];return n}function E(n,t){for(var e in n)if("__source"!==e&&!(e in t))return !0;for(var r in t)if("__source"!==r&&n[r]!==t[r])return !0;return !1}function M(n,t){this.props=n,this.context=t;}(M.prototype=new C).isPureReactComponent=!0,M.prototype.shouldComponentUpdate=function(n,t){return E(this.props,n)||E(this.state,t)};var T=l$2.__b;l$2.__b=function(n){n.type&&n.type.__f&&n.ref&&(n.props.ref=n.ref,n.ref=null),T&&T(n);};var O=l$2.__e;l$2.__e=function(n,t,e,r){if(n.then)for(var u,o=t;o=o.__;)if((u=o.__c)&&u.__c)return null==t.__e&&(t.__e=e.__e,t.__k=e.__k),u.__c(n,t);O(n,t,e,r);};var U=l$2.unmount;function V(n,t,e){return n&&(n.__c&&n.__c.__H&&(n.__c.__H.__.forEach(function(n){"function"==typeof n.__c&&n.__c();}),n.__c.__H=null),null!=(n=g({},n)).__c&&(n.__c.__P===e&&(n.__c.__P=t),n.__c.__e=!0,n.__c=null),n.__k=n.__k&&n.__k.map(function(n){return V(n,t,e)})),n}function W(n,t,e){return n&&e&&(n.__v=null,n.__k=n.__k&&n.__k.map(function(n){return W(n,t,e)}),n.__c&&n.__c.__P===t&&(n.__e&&e.appendChild(n.__e),n.__c.__e=!0,n.__c.__P=e)),n}function P(){this.__u=0,this.o=null,this.__b=null;}function j(n){var t=n.__&&n.__.__c;return t&&t.__a&&t.__a(n)}function B(){this.i=null,this.l=null;}l$2.unmount=function(n){var t=n.__c;t&&(t.__z=!0),t&&t.__R&&t.__R(),t&&32&n.__u&&(n.type=null),U&&U(n);},(P.prototype=new C).__c=function(n,t){var e=t.__c,r=this;null==r.o&&(r.o=[]),r.o.push(e);var u=j(r.__v),o=!1,i=function(){o||r.__z||(o=!0,e.__R=null,u?u(c):c());};e.__R=i;var l=e.__P;e.__P=null;var c=function(){if(!--r.__u){if(r.state.__a){var n=r.state.__a;r.__v.__k[0]=W(n,n.__c.__P,n.__c.__O);}var t;for(r.setState({__a:r.__b=null});t=r.o.pop();)t.__P=l,t.forceUpdate();}};r.__u++||32&t.__u||r.setState({__a:r.__b=r.__v.__k[0]}),n.then(i,i);},P.prototype.componentWillUnmount=function(){this.o=[];},P.prototype.render=function(n,e){if(this.__b){if(this.__v.__k){var r=document.createElement("div"),o=this.__v.__k[0].__c;this.__v.__k[0]=V(this.__b,r,o.__O=o.__P);}this.__b=null;}var i=e.__a&&k$1(S,null,n.fallback);return i&&(i.__u&=-33),[k$1(S,null,e.__a?null:n.children),i]};var H=function(n,t,e){if(++e[1]===e[0]&&n.l.delete(t),n.props.revealOrder&&("t"!==n.props.revealOrder[0]||!n.l.size))for(e=n.i;e;){for(;e.length>3;)e.pop()();if(e[1]>>1,1),e.h.removeChild(n);}};}R(k$1(Z,{context:e.context},n.__v),e.v);}function $(n,e){var r=k$1(Y,{__v:n,h:e});return r.containerInfo=e,r}(B.prototype=new C).__a=function(n){var t=this,e=j(t.__v),r=t.l.get(n);return r[0]++,function(u){var o=function(){t.props.revealOrder?(r.push(u),H(t,n,r)):u();};e?e(o):o();}},B.prototype.render=function(n){this.i=null,this.l=new Map;var t=F(n.children);n.revealOrder&&"b"===n.revealOrder[0]&&t.reverse();for(var e=t.length;e--;)this.l.set(t[e],this.i=[1,0,this.i]);return n.children},B.prototype.componentDidUpdate=B.prototype.componentDidMount=function(){var n=this;this.l.forEach(function(t,e){H(n,e,t);});};var q="undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103,G=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,J=/^on(Ani|Tra|Tou|BeforeInp|Compo)/,K=/[A-Z0-9]/g,Q="undefined"!=typeof document,X=function(n){return ("undefined"!=typeof Symbol&&"symbol"==typeof Symbol()?/fil|che|rad/:/fil|che|ra/).test(n)};function nn(n,t,e){return null==t.__k&&(t.textContent=""),R(n,t),"function"==typeof e&&e(),n?n.__c:null}C.prototype.isReactComponent=!0,["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(t){Object.defineProperty(C.prototype,t,{configurable:!0,get:function(){return this["UNSAFE_"+t]},set:function(n){Object.defineProperty(this,t,{configurable:!0,writable:!0,value:n});}});});var en=l$2.event;l$2.event=function(n){return en&&(n=en(n)),n.persist=function(){},n.isPropagationStopped=function(){return this.cancelBubble},n.isDefaultPrevented=function(){return this.defaultPrevented},n.nativeEvent=n};var un={configurable:!0,get:function(){return this.class}},on=l$2.vnode;l$2.vnode=function(n){"string"==typeof n.type&&function(n){var t=n.props,e=n.type,u={},o=-1==e.indexOf("-");for(var i in t){var l=t[i];if(!("value"===i&&"defaultValue"in t&&null==l||Q&&"children"===i&&"noscript"===e||"class"===i||"className"===i)){var c=i.toLowerCase();"defaultValue"===i&&"value"in t&&null==t.value?i="value":"download"===i&&!0===l?l="":"translate"===c&&"no"===l?l=!1:"o"===c[0]&&"n"===c[1]?"ondoubleclick"===c?i="ondblclick":"onchange"!==c||"input"!==e&&"textarea"!==e||X(t.type)?"onfocus"===c?i="onfocusin":"onblur"===c?i="onfocusout":J.test(i)&&(i=c):c=i="oninput":o&&G.test(i)?i=i.replace(K,"-$&").toLowerCase():null===l&&(l=void 0),"oninput"===c&&u[i=c]&&(i="oninputCapture"),u[i]=l;}}"select"==e&&(u.multiple&&Array.isArray(u.value)&&(u.value=F(t.children).forEach(function(n){n.props.selected=-1!=u.value.indexOf(n.props.value);})),null!=u.defaultValue&&(u.value=F(t.children).forEach(function(n){n.props.selected=u.multiple?-1!=u.defaultValue.indexOf(n.props.value):u.defaultValue==n.props.value;}))),t.class&&!t.className?(u.class=t.class,Object.defineProperty(u,"className",un)):t.className&&(u.class=u.className=t.className),n.props=u;}(n),n.$$typeof=q,on&&on(n);};var ln=l$2.__r;l$2.__r=function(n){ln&&ln(n),n.__c;};var cn=l$2.diffed;l$2.diffed=function(n){cn&&cn(n);var t=n.props,e=n.__e;null!=e&&"textarea"===n.type&&"value"in t&&t.value!==e.value&&(e.value=null==t.value?"":t.value);};function hn(n){return !!n&&n.$$typeof===q}function pn(n){return !!n.__k&&(R(null,n),!0)}var bn=function(n,t){var r=l$2.debounceRendering;l$2.debounceRendering=function(n){return n()};var u=n(t);return l$2.debounceRendering=r,u}; + + function memoize(workerFunc, resEquality, teardownFunc) { + let currentArgs; + let currentRes; + return function (...newArgs) { + if (!currentArgs) { + currentRes = workerFunc.apply(this, newArgs); + } + else if (!isArraysEqual(currentArgs, newArgs)) { + if (teardownFunc) { + teardownFunc(currentRes); + } + let res = workerFunc.apply(this, newArgs); + if (!resEquality || !resEquality(res, currentRes)) { + currentRes = res; + } + } + currentArgs = newArgs; + return currentRes; + }; + } + function memoizeObjArg(workerFunc, resEquality, teardownFunc) { + let currentArg; + let currentRes; + return (newArg) => { + if (!currentArg) { + currentRes = workerFunc.call(this, newArg); + } + else if (!isPropsEqualShallow(currentArg, newArg)) { + if (teardownFunc) { + teardownFunc(currentRes); + } + let res = workerFunc.call(this, newArg); + if (!resEquality || !resEquality(res, currentRes)) { + currentRes = res; + } + } + currentArg = newArg; + return currentRes; + }; + } + + const ViewContextType = X$1({}); // for Components + function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, nowManager, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, baseId, registerInteractiveComponent, unregisterInteractiveComponent) { + return { + dateEnv, + nowManager, + options: viewOptions, + pluginHooks, + emitter, + dispatch, + getCurrentData, + calendarApi, + viewSpec, + viewApi, + dateProfileGenerator, + baseId, + registerInteractiveComponent, + unregisterInteractiveComponent, + }; + } + + /* eslint max-classes-per-file: off */ + class PureComponent extends C { + // debug: boolean + shouldComponentUpdate(nextProps, nextState) { + return !isPropsEqualWithMap(this.props, nextProps, this.propEquality /*, this.debug && 'props' */) || + !isPropsEqualWithMap(this.state, nextState, this.stateEquality /*, this.debug && 'state' */); + } + } + PureComponent.addPropsEquality = addPropsEquality; + PureComponent.addStateEquality = addStateEquality; + PureComponent.contextType = ViewContextType; + PureComponent.prototype.propEquality = {}; + PureComponent.prototype.stateEquality = {}; + class BaseComponent extends PureComponent { + } + BaseComponent.contextType = ViewContextType; + function addPropsEquality(propEquality) { + let hash = Object.create(this.prototype.propEquality); + Object.assign(hash, propEquality); + this.prototype.propEquality = hash; + } + function addStateEquality(stateEquality) { + let hash = Object.create(this.prototype.stateEquality); + Object.assign(hash, stateEquality); + this.prototype.stateEquality = hash; + } + // use other one + function setRef(ref, current) { + if (typeof ref === 'function') { + ref(current); + } + else if (ref) { + // see https://github.com/facebook/react/issues/13029 + ref.current = current; + } + } + + class ContentInjector extends BaseComponent { + constructor() { + super(...arguments); + this.id = guid(); + this.queuedDomNodes = []; + this.currentDomNodes = []; + this.handleEl = (el) => { + this.el = el; + const { options } = this.context; + const { generatorName } = this.props; + if (!options.customRenderingReplaces || !hasCustomRenderingHandler(generatorName, options)) { + this.updateElRef(el); + } + }; + this.updateElRef = (el) => { + if (this.props.elRef) { + setRef(this.props.elRef, el); + } + }; + } + render() { + const { props, context } = this; + const { options } = context; + const { customGenerator, defaultGenerator, renderProps } = props; + const attrs = buildElAttrs(props, '', this.handleEl); + let useDefault = false; + let innerContent; + let queuedDomNodes = []; + let currentGeneratorMeta; + if (customGenerator != null) { + const customGeneratorRes = typeof customGenerator === 'function' ? + customGenerator(renderProps) : + customGenerator; + if (customGeneratorRes === true) { + useDefault = true; + // NOTE: see how mergeContentInjectors also uses `true` to signal useDefault + } + else { + const isObject = customGeneratorRes && typeof customGeneratorRes === 'object'; // non-null + if (isObject && ('html' in customGeneratorRes)) { + attrs.dangerouslySetInnerHTML = { __html: customGeneratorRes.html }; + } + else if (isObject && ('domNodes' in customGeneratorRes)) { + queuedDomNodes = Array.prototype.slice.call(customGeneratorRes.domNodes); + } + else if (isObject + ? hn(customGeneratorRes) // vdom node + : typeof customGeneratorRes !== 'function' // primitive value (like string or number) + ) { + // use in vdom + innerContent = customGeneratorRes; + } + else { + // an exotic object for handleCustomRendering + currentGeneratorMeta = customGeneratorRes; + } + } + } + else { + useDefault = !hasCustomRenderingHandler(props.generatorName, options); + } + if (useDefault && defaultGenerator) { + innerContent = defaultGenerator(renderProps); + } + this.queuedDomNodes = queuedDomNodes; + this.currentGeneratorMeta = currentGeneratorMeta; + return k$1(props.tag, attrs, innerContent); + } + componentDidMount() { + this.applyQueueudDomNodes(); + this.triggerCustomRendering(true); + } + componentDidUpdate() { + this.applyQueueudDomNodes(); + this.triggerCustomRendering(true); + } + componentWillUnmount() { + this.triggerCustomRendering(false); // TODO: different API for removal? + } + triggerCustomRendering(isActive) { + const { props, context } = this; + const { handleCustomRendering, customRenderingMetaMap } = context.options; + if (handleCustomRendering) { + const generatorMeta = this.currentGeneratorMeta ?? + customRenderingMetaMap?.[props.generatorName]; + if (generatorMeta) { + handleCustomRendering({ + id: this.id, + isActive, + containerEl: this.el, + reportNewContainerEl: this.updateElRef, // front-end framework tells us about new container els + generatorMeta, + ...props, + }); + } + } + } + applyQueueudDomNodes() { + const { queuedDomNodes, currentDomNodes } = this; + const { el } = this; + if (!isArraysEqual(queuedDomNodes, currentDomNodes)) { + for (const domNode of currentDomNodes) { + domNode.remove(); + } + for (let newNode of queuedDomNodes) { + el.appendChild(newNode); + } + this.currentDomNodes = queuedDomNodes; + } + } + } + ContentInjector.addPropsEquality({ + renderProps: isPropsEqualShallow, + attrs: isNonHandlerPropsEqual, + style: isPropsEqualShallow, + }); + // Util + /* + Does UI-framework provide custom way of rendering that does not use Preact VDOM + AND does the calendar's options define custom rendering? + AKA. Should we NOT render the default content? + */ + function hasCustomRenderingHandler(generatorName, options) { + return Boolean(options.handleCustomRendering && + generatorName && + options.customRenderingMetaMap?.[generatorName]); + } + function buildElAttrs(props, className, elRef) { + const attrs = { ...props.attrs, ref: elRef }; + if (props.className || className) { + attrs.className = joinClassNames(className, props.className, attrs.className); + } + if (props.style) { + attrs.style = props.style; + } + return attrs; + } + + const RenderId = X$1(0); + + class ContentContainer extends C { + constructor() { + super(...arguments); + this.InnerContent = InnerContentInjector.bind(undefined, this); + this.handleEl = (el) => { + this.el = el; + if (this.props.elRef) { + setRef(this.props.elRef, el); + if (el && this.didMountMisfire) { + this.componentDidMount(); + } + } + }; + } + render() { + const { props } = this; + const generatedClassName = generateClassName(props.classNameGenerator, props.renderProps); + if (props.children) { + const attrs = buildElAttrs(props, generatedClassName, this.handleEl); + const children = props.children(this.InnerContent, props.renderProps, attrs); + if (props.tag) { + return k$1(props.tag, attrs, children); + } + else { + return children; + } + } + else { + return k$1((ContentInjector), { + ...props, + elRef: this.handleEl, + tag: props.tag || 'div', + className: joinClassNames(props.className, generatedClassName), + renderId: this.context, + }); + } + } + componentDidMount() { + if (this.el) { + this.props.didMount?.({ + ...this.props.renderProps, + el: this.el, + }); + } + else { + this.didMountMisfire = true; + } + } + componentWillUnmount() { + this.props.willUnmount?.({ + ...this.props.renderProps, + el: this.el, + }); + } + } + ContentContainer.contextType = RenderId; + function InnerContentInjector(containerComponent, props) { + const parentProps = containerComponent.props; + return k$1((ContentInjector), { + renderProps: parentProps.renderProps, + generatorName: parentProps.generatorName, + customGenerator: parentProps.customGenerator, + defaultGenerator: parentProps.defaultGenerator, + renderId: containerComponent.context, + ...props, + }); + } + // Utils + function generateClassName(classNameGenerator, renderProps) { + return (typeof classNameGenerator === 'function' ? + classNameGenerator(renderProps) : + classNameGenerator) || ''; // handles undefined + } + function renderText$1(renderProps) { + return renderProps.text; + } + + function getIsHeightAuto(options) { + return options.height === 'auto' || options.contentHeight === 'auto'; + } + function getTableHeaderSticky(options) { + let { tableHeaderSticky } = options; + if (tableHeaderSticky == null || tableHeaderSticky === 'auto') { + tableHeaderSticky = getIsHeightAuto(options); + } + return tableHeaderSticky; + } + function getFooterScrollbarSticky(options) { + const isHeightAuto = getIsHeightAuto(options); + let { footerScrollbarSticky } = options; + if (footerScrollbarSticky == null || footerScrollbarSticky === 'auto') { + footerScrollbarSticky = isHeightAuto; + } + return Boolean(footerScrollbarSticky) && isHeightAuto; + } + function getScrollerSyncerClass(pluginHooks) { + const ScrollerSyncer = pluginHooks.scrollerSyncerClass; + if (!ScrollerSyncer) { + throw new RangeError('Must import @fullcalendar/scrollgrid'); + } + return ScrollerSyncer; + } + + class NowTimerRunner { + constructor(handleChange) { + this.handleChange = handleChange; + this.isMounted = false; + this.handleRefresh = () => { + let timing = this.computeTiming(); + if (timing.nowDate.valueOf() !== this.nowDate.valueOf()) { + this.nowDate = timing.nowDate; + this.todayRange = timing.todayRange; + this.handleChange(); + } + this.clearTimeout(); + this.setTimeout(timing.waitMs); + }; + this.handleVisibilityChange = () => { + if (!document.hidden) { + this.handleRefresh(); + } + }; + } + update(input) { + if (!this.isMounted) { + this.isMounted = true; + // init inputs + this.unit = input.unit; + this.unitValue = input.unitValue; + this.nowIndicatorSnap = input.nowIndicatorSnap; + this.nowManager = input.nowManager; + this.dateEnv = input.dateEnv; + // init outputs + const timing = this.computeTiming(); + this.nowDate = timing.nowDate; + this.todayRange = timing.todayRange; + // init listeners + this.setTimeout(); + this.nowManager.addResetListener(this.handleRefresh); + // fired tab becomes visible after being hidden + // SSR check. CalendarDataManager calls top-level sync :( + if (typeof document !== 'undefined') { + document.addEventListener('visibilitychange', this.handleVisibilityChange); + } + } + else if (input.unit !== this.unit || + input.unitValue !== this.unitValue || + input.nowIndicatorSnap !== this.nowIndicatorSnap || + input.nowManager !== this.nowManager || + input.dateEnv !== this.dateEnv) { + // update inputs + this.unit = input.unit; + this.unitValue = input.unitValue; + this.nowIndicatorSnap = input.nowIndicatorSnap; + this.nowManager = input.nowManager; + this.dateEnv = input.dateEnv; + this.clearTimeout(); + this.setTimeout(); + } + return { + nowDate: this.nowDate, + todayRange: this.todayRange, + }; + } + destroy() { + if (this.isMounted) { + this.isMounted = false; + this.clearTimeout(); + this.nowManager.removeResetListener(this.handleRefresh); + // SSR check. CalendarDataManager calls top-level sync :( + if (typeof document !== 'undefined') { + document.removeEventListener('visibilitychange', this.handleVisibilityChange); + } + } + } + computeTiming() { + let unroundedNow = this.nowManager.getDateMarker(); + let { unit, unitValue, nowIndicatorSnap, dateEnv } = this; + if (nowIndicatorSnap === 'auto') { + nowIndicatorSnap = + // large unit? + /year|month|week|day/.test(unit) || + // if slotDuration 30 mins for example, would NOT appear to snap (legacy behavior) + (unitValue || 1) === 1; + } + let nowDate; + let waitMs; + if (nowIndicatorSnap) { + nowDate = dateEnv.startOf(unroundedNow, unit); // aka currentUnitStart + let nextUnitStart = dateEnv.add(nowDate, createDuration(1, unit)); + waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf(); + } + else { + nowDate = unroundedNow; + waitMs = 1000 * 60; // 1 minute + } + // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342) + // ensure no longer than a day + waitMs = Math.min(1000 * 60 * 60 * 24, waitMs); + return { + nowDate, + todayRange: buildDayRange(nowDate), + waitMs, + }; + } + setTimeout(waitMs = this.computeTiming().waitMs) { + // NOTE: timeout could take longer than expected if tab sleeps, + // which is why we listen to 'visibilitychange' + this.timeoutId = setTimeout(() => { + // NOTE: timeout could also return *earlier* than expected, and we need to wait like 2 ms more + // This is why use use same waitMs from computeTiming + const timing = this.computeTiming(); + this.nowDate = timing.nowDate; + this.todayRange = timing.todayRange; + this.handleChange(); + this.setTimeout(timing.waitMs); + }, waitMs); + } + clearTimeout() { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + } + } + } + function buildDayRange(date) { + let start = startOfDay(date); + let end = addDays(start, 1); + return { start, end }; + } + + class DateProfileGenerator { + constructor(props) { + this.props = props; + this.initHiddenDays(); + } + /* Date Range Computation + ------------------------------------------------------------------------------------------------------------------*/ + // Builds a structure with info about what the dates/ranges will be for the "prev" view. + buildPrev(currentDateProfile, currentDate, nowDate, forceToValid) { + let { dateEnv } = this.props; + let prevDate = dateEnv.subtract(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month + currentDateProfile.dateIncrement); + return this.build(prevDate, nowDate, -1, forceToValid); + } + // Builds a structure with info about what the dates/ranges will be for the "next" view. + buildNext(currentDateProfile, currentDate, nowDate, forceToValid) { + let { dateEnv } = this.props; + let nextDate = dateEnv.add(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month + currentDateProfile.dateIncrement); + return this.build(nextDate, nowDate, 1, forceToValid); + } + // Builds a structure holding dates/ranges for rendering around the given date. + // Optional direction param indicates whether the date is being incremented/decremented + // from its previous value. decremented = -1, incremented = 1 (default). + build(currentDate, nowDate, direction, forceToValid = true) { + let { props } = this; + let validRange; + let currentInfo; + let isRangeAllDay; + let renderRange; + let activeRange; + let isValid; + validRange = this.buildValidRange(nowDate); + validRange = this.trimHiddenDays(validRange); + if (forceToValid) { + currentDate = constrainMarkerToRange(currentDate, validRange); + } + currentInfo = this.buildCurrentRangeInfo(currentDate, direction); + isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit); + renderRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.range), currentInfo.unit, isRangeAllDay); + renderRange = this.trimHiddenDays(renderRange); + activeRange = renderRange; + if (!props.showNonCurrentDates) { + activeRange = intersectRanges(activeRange, currentInfo.range); + } + activeRange = this.adjustActiveRange(activeRange); + activeRange = intersectRanges(activeRange, validRange); // might return null + // it's invalid if the originally requested date is not contained, + // or if the range is completely outside of the valid range. + isValid = rangesIntersect(currentInfo.range, validRange); + // HACK: constrain to render-range so `currentDate` is more useful to view rendering + if (!rangeContainsMarker(renderRange, currentDate)) { + currentDate = renderRange.start; + } + return { + currentDate, + // constraint for where prev/next operations can go and where events can be dragged/resized to. + // an object with optional start and end properties. + validRange, + // range the view is formally responsible for. + // for example, a month view might have 1st-31st, excluding padded dates + currentRange: currentInfo.range, + // name of largest unit being displayed, like "month" or "week" + currentRangeUnit: currentInfo.unit, + isRangeAllDay, + // dates that display events and accept drag-n-drop + // will be `null` if no dates accept events + activeRange, + // date range with a rendered skeleton + // includes not-active days that need some sort of DOM + renderRange, + // Duration object that denotes the first visible time of any given day + slotMinTime: props.slotMinTime, + // Duration object that denotes the exclusive visible end time of any given day + slotMaxTime: props.slotMaxTime, + isValid, + // how far the current date will move for a prev/next operation + dateIncrement: this.buildDateIncrement(currentInfo.duration), + // pass a fallback (might be null) ^ + }; + } + // Builds an object with optional start/end properties. + // Indicates the minimum/maximum dates to display. + // not responsible for trimming hidden days. + buildValidRange(nowDate) { + let input = this.props.validRangeInput; + let simpleInput = typeof input === 'function' + ? input.call(this.props.calendarApi, this.props.dateEnv.toDate(nowDate)) + : input; + return this.refineRange(simpleInput) || + { start: null, end: null }; // completely open-ended + } + // Builds a structure with info about the "current" range, the range that is + // highlighted as being the current month for example. + // See build() for a description of `direction`. + // Guaranteed to have `range` and `unit` properties. `duration` is optional. + buildCurrentRangeInfo(date, direction) { + let { props } = this; + let duration = null; + let unit = null; + let range = null; + let dayCount; + if (props.duration) { + duration = props.duration; + unit = props.durationUnit; + range = this.buildRangeFromDuration(date, direction, duration, unit); + } + else if ((dayCount = this.props.dayCount)) { + unit = 'day'; + range = this.buildRangeFromDayCount(date, direction, dayCount); + } + else if ((range = this.buildCustomVisibleRange(date))) { + unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit; + } + else { + duration = this.getFallbackDuration(); + unit = greatestDurationDenominator(duration).unit; + range = this.buildRangeFromDuration(date, direction, duration, unit); + } + return { duration, unit, range }; + } + getFallbackDuration() { + return createDuration({ day: 1 }); + } + // Returns a new activeRange to have time values (un-ambiguate) + // slotMinTime or slotMaxTime causes the range to expand. + adjustActiveRange(range) { + let { dateEnv, usesMinMaxTime, slotMinTime, slotMaxTime } = this.props; + let { start, end } = range; + if (usesMinMaxTime) { + // expand active range if slotMinTime is negative (why not when positive?) + if (asRoughDays(slotMinTime) < 0) { + start = startOfDay(start); // necessary? + start = dateEnv.add(start, slotMinTime); + } + // expand active range if slotMaxTime is beyond one day (why not when negative?) + if (asRoughDays(slotMaxTime) > 1) { + end = startOfDay(end); // necessary? + end = addDays(end, -1); + end = dateEnv.add(end, slotMaxTime); + } + } + return { start, end }; + } + // Builds the "current" range when it is specified as an explicit duration. + // `unit` is the already-computed greatestDurationDenominator unit of duration. + buildRangeFromDuration(date, direction, duration, unit) { + let { dateEnv, dateAlignment } = this.props; + let start; + let end; + let res; + // compute what the alignment should be + if (!dateAlignment) { + let { dateIncrement } = this.props; + if (dateIncrement) { + // use the smaller of the two units + if (asRoughMs(dateIncrement) < asRoughMs(duration)) { + dateAlignment = greatestDurationDenominator(dateIncrement).unit; + } + else { + dateAlignment = unit; + } + } + else { + dateAlignment = unit; + } + } + // if the view displays a single day or smaller + if (asRoughDays(duration) <= 1) { + if (this.isHiddenDay(start)) { + start = this.skipHiddenDays(start, direction); + start = startOfDay(start); + } + } + function computeRes() { + start = dateEnv.startOf(date, dateAlignment); + end = dateEnv.add(start, duration); + res = { start, end }; + } + computeRes(); + // if range is completely enveloped by hidden days, go past the hidden days + if (!this.trimHiddenDays(res)) { + date = this.skipHiddenDays(date, direction); + computeRes(); + } + return res; + } + // Builds the "current" range when a dayCount is specified. + buildRangeFromDayCount(date, direction, dayCount) { + let { dateEnv, dateAlignment } = this.props; + let runningCount = 0; + let start = date; + let end; + if (dateAlignment) { + start = dateEnv.startOf(start, dateAlignment); + } + start = startOfDay(start); + start = this.skipHiddenDays(start, direction); + end = start; + do { + end = addDays(end, 1); + if (!this.isHiddenDay(end)) { + runningCount += 1; + } + } while (runningCount < dayCount); + return { start, end }; + } + // Builds a normalized range object for the "visible" range, + // which is a way to define the currentRange and activeRange at the same time. + buildCustomVisibleRange(date) { + let { props } = this; + let input = props.visibleRangeInput; + let simpleInput = typeof input === 'function' + ? input.call(props.calendarApi, props.dateEnv.toDate(date)) + : input; + let range = this.refineRange(simpleInput); + if (range && (range.start == null || range.end == null)) { + return null; + } + return range; + } + // Computes the range that will represent the element/cells for *rendering*, + // but which may have voided days/times. + // not responsible for trimming hidden days. + buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) { + return currentRange; + } + // Compute the duration value that should be added/substracted to the current date + // when a prev/next operation happens. + buildDateIncrement(fallback) { + let { dateIncrement } = this.props; + let customAlignment; + if (dateIncrement) { + return dateIncrement; + } + if ((customAlignment = this.props.dateAlignment)) { + return createDuration(1, customAlignment); + } + if (fallback) { + return fallback; + } + return createDuration({ days: 1 }); + } + refineRange(rangeInput) { + if (rangeInput) { + let range = parseRange(rangeInput, this.props.dateEnv); + if (range) { + range = computeVisibleDayRange(range); + } + return range; + } + return null; + } + /* Hidden Days + ------------------------------------------------------------------------------------------------------------------*/ + // Initializes internal variables related to calculating hidden days-of-week + initHiddenDays() { + let hiddenDays = this.props.hiddenDays || []; // array of day-of-week indices that are hidden + let isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) + let dayCnt = 0; + let i; + if (this.props.weekends === false) { + hiddenDays.push(0, 6); // 0=sunday, 6=saturday + } + for (i = 0; i < 7; i += 1) { + if (!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)) { + dayCnt += 1; + } + } + if (!dayCnt) { + throw new Error('invalid hiddenDays'); // all days were hidden? bad. + } + this.isHiddenDayHash = isHiddenDayHash; + } + // Remove days from the beginning and end of the range that are computed as hidden. + // If the whole range is trimmed off, returns null + trimHiddenDays(range) { + let { start, end } = range; + if (start) { + start = this.skipHiddenDays(start); + } + if (end) { + end = this.skipHiddenDays(end, -1, true); + } + if (start == null || end == null || start < end) { + return { start, end }; + } + return null; + } + // Is the current day hidden? + // `day` is a day-of-week index (0-6), or a Date (used for UTC) + isHiddenDay(day) { + if (day instanceof Date) { + day = day.getUTCDay(); + } + return this.isHiddenDayHash[day]; + } + // Incrementing the current day until it is no longer a hidden day, returning a copy. + // DOES NOT CONSIDER validRange! + // If the initial value of `date` is not a hidden day, don't do anything. + // Pass `isExclusive` as `true` if you are dealing with an end date. + // `inc` defaults to `1` (increment one day forward each time) + skipHiddenDays(date, inc = 1, isExclusive = false) { + while (this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]) { + date = addDays(date, inc); + } + return date; + } + } + // Utils + // ------------------------------------------------------------------------------------------------- + function computeMajorUnit(dateProfile, dateEnv) { + const { currentRange } = dateProfile; + if (dateProfile.currentRangeUnit === 'year') { + if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) { + return 'year'; + } + else { + return 'month'; + } + } + else if (dateProfile.currentRangeUnit === 'month') { + if (dateEnv.diffWholeMonths(currentRange.start, currentRange.end) > 1) { + return 'month'; + } + } + else if (dateProfile.currentRangeUnit === 'week') { + if (diffWholeWeeks(currentRange.start, currentRange.end) > 1) { + return 'week'; + } + } + else if (dateProfile.currentRangeUnit === 'day') { + if (diffWholeDays(currentRange.start, currentRange.end) > 1) { + return 'day'; + } + } + } + function isMajorUnit(dateMarker, majorUnit, dateEnv) { + const isStartOfDay = dateMarker.valueOf() === startOfDay(dateMarker).valueOf(); + if (isStartOfDay) { + if (majorUnit === 'year') { + return !dateEnv.getMonth(dateMarker) && dateEnv.getDay(dateMarker) === 1; + } + else if (majorUnit === 'month') { + return dateEnv.getDay(dateMarker) === 1; + } + else if (majorUnit === 'week') { + return dateMarker.getUTCDay() === dateEnv.weekDow; + } + else if (majorUnit === 'day') { + return true; + } + } + return false; + } + + function reduceEventStore(eventStore, action, eventSources, dateProfile, context) { + switch (action.type) { + case 'RECEIVE_EVENTS': // raw + return receiveRawEvents(eventStore, eventSources[action.sourceId], action.fetchId, action.fetchRange, action.rawEvents, context); + case 'RESET_RAW_EVENTS': + return resetRawEvents(eventStore, eventSources[action.sourceId], action.rawEvents, dateProfile.activeRange, context); + case 'ADD_EVENTS': // already parsed, but not expanded + return addEvent(eventStore, action.eventStore, // new ones + dateProfile ? dateProfile.activeRange : null, context); + case 'RESET_EVENTS': + return action.eventStore; + case 'MERGE_EVENTS': // already parsed and expanded + return mergeEventStores(eventStore, action.eventStore); + case 'PREV': // TODO: how do we track all actions that affect dateProfile :( + case 'NEXT': + case 'CHANGE_DATE': + case 'CHANGE_VIEW_TYPE': + if (dateProfile) { + return expandRecurring(eventStore, dateProfile.activeRange, context); + } + return eventStore; + case 'REMOVE_EVENTS': + return excludeSubEventStore(eventStore, action.eventStore); + case 'REMOVE_EVENT_SOURCE': + return excludeEventsBySourceId(eventStore, action.sourceId); + case 'REMOVE_ALL_EVENT_SOURCES': + return filterEventStoreDefs(eventStore, (eventDef) => (!eventDef.sourceId // only keep events with no source id + )); + case 'REMOVE_ALL_EVENTS': + return createEmptyEventStore(); + default: + return eventStore; + } + } + function receiveRawEvents(eventStore, eventSource, fetchId, fetchRange, rawEvents, context) { + if (eventSource && // not already removed + fetchId === eventSource.latestFetchId // TODO: wish this logic was always in event-sources + ) { + let subset = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context); + if (fetchRange) { + subset = expandRecurring(subset, fetchRange, context); + } + return mergeEventStores(excludeEventsBySourceId(eventStore, eventSource.sourceId), subset); + } + return eventStore; + } + function resetRawEvents(existingEventStore, eventSource, rawEvents, activeRange, context) { + const { defIdMap, instanceIdMap } = buildPublicIdMaps(existingEventStore); + let newEventStore = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context, false, defIdMap, instanceIdMap); + return expandRecurring(newEventStore, activeRange, context); + } + function transformRawEvents(rawEvents, eventSource, context) { + let calEachTransform = context.options.eventDataTransform; + let sourceEachTransform = eventSource ? eventSource.eventDataTransform : null; + if (sourceEachTransform) { + rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform); + } + if (calEachTransform) { + rawEvents = transformEachRawEvent(rawEvents, calEachTransform); + } + return rawEvents; + } + function transformEachRawEvent(rawEvents, func) { + let refinedEvents; + if (!func) { + refinedEvents = rawEvents; + } + else { + refinedEvents = []; + for (let rawEvent of rawEvents) { + let refinedEvent = func(rawEvent); + if (refinedEvent) { + refinedEvents.push(refinedEvent); + } + else if (refinedEvent == null) { + refinedEvents.push(rawEvent); + } // if a different falsy value, do nothing + } + } + return refinedEvents; + } + function addEvent(eventStore, subset, expandRange, context) { + if (expandRange) { + subset = expandRecurring(subset, expandRange, context); + } + return mergeEventStores(eventStore, subset); + } + function rezoneEventStoreDates(eventStore, oldDateEnv, newDateEnv) { + let { defs } = eventStore; + let instances = mapHash(eventStore.instances, (instance) => { + let def = defs[instance.defId]; + if (def.allDay) { + return instance; // isn't dependent on timezone + } + return { + ...instance, + range: { + start: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.start)), + end: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.end)), + }, + }; + }); + return { defs, instances }; + } + function excludeEventsBySourceId(eventStore, sourceId) { + return filterEventStoreDefs(eventStore, (eventDef) => eventDef.sourceId !== sourceId); + } + // QUESTION: why not just return instances? do a general object-property-exclusion util + function excludeInstances(eventStore, removals) { + return { + defs: eventStore.defs, + instances: filterHash(eventStore.instances, (instance) => !removals[instance.instanceId]), + }; + } + function buildPublicIdMaps(eventStore) { + const { defs, instances } = eventStore; + const defIdMap = {}; + const instanceIdMap = {}; + for (let defId in defs) { + const def = defs[defId]; + const { publicId } = def; + if (publicId) { + defIdMap[publicId] = defId; + } + } + for (let instanceId in instances) { + const instance = instances[instanceId]; + const def = defs[instance.defId]; + const { publicId } = def; + if (publicId) { + instanceIdMap[publicId] = instanceId; + } + } + return { defIdMap, instanceIdMap }; + } + + class Interaction { + constructor(settings) { + this.component = settings.component; + this.isHitComboAllowed = settings.isHitComboAllowed || null; + } + destroy() { + } + } + function parseInteractionSettings(component, input) { + return { + component, + el: input.el, + useEventCenter: input.useEventCenter != null ? input.useEventCenter : true, + isHitComboAllowed: input.isHitComboAllowed || null, + }; + } + function interactionSettingsToStore(settings) { + return { + [settings.component.uid]: settings, + }; + } + // global state + const interactionSettingsStore = {}; + + class Emitter { + constructor() { + this.handlers = {}; + this.thisContext = null; + } + setThisContext(thisContext) { + this.thisContext = thisContext; + } + setOptions(options) { + this.options = options; + } + on(type, handler) { + addToHash(this.handlers, type, handler); + } + off(type, handler) { + removeFromHash(this.handlers, type, handler); + } + trigger(type, ...args) { + let attachedHandlers = this.handlers[type] || []; + let optionHandler = this.options && this.options[type]; + let handlers = [].concat(optionHandler || [], attachedHandlers); + for (let handler of handlers) { + handler.apply(this.thisContext, args); + } + } + hasHandlers(type) { + return Boolean((this.handlers[type] && this.handlers[type].length) || + (this.options && this.options[type])); + } + } + function addToHash(hash, type, handler) { + (hash[type] || (hash[type] = [])) + .push(handler); + } + function removeFromHash(hash, type, handler) { + if (handler) { + if (hash[type]) { + hash[type] = hash[type].filter((func) => func !== handler); + } + } + else { + delete hash[type]; // remove all handler funcs for this type + } + } + + // TODO: easier way to add new hooks? need to update a million things + function refinePluginDef(input) { + return { + name: input.name, + premiumReleaseDate: input.premiumReleaseDate ? new Date(input.premiumReleaseDate) : undefined, + reducers: input.reducers || [], + isLoadingFuncs: input.isLoadingFuncs || [], + contextInit: [].concat(input.contextInit || []), + eventRefiners: input.eventRefiners || {}, + eventDefMemberAdders: input.eventDefMemberAdders || [], + eventSourceRefiners: input.eventSourceRefiners || {}, + isDraggableTransformers: input.isDraggableTransformers || [], + eventDragMutationMassagers: input.eventDragMutationMassagers || [], + eventDefMutationAppliers: input.eventDefMutationAppliers || [], + dateSelectionTransformers: input.dateSelectionTransformers || [], + datePointTransforms: input.datePointTransforms || [], + dateSpanTransforms: input.dateSpanTransforms || [], + views: input.views || {}, + viewPropsTransformers: input.viewPropsTransformers || [], + isPropsValid: input.isPropsValid || null, + externalDefTransforms: input.externalDefTransforms || [], + viewContainerAppends: input.viewContainerAppends || [], + eventDropTransformers: input.eventDropTransformers || [], + componentInteractions: input.componentInteractions || [], + calendarInteractions: input.calendarInteractions || [], + eventSourceDefs: input.eventSourceDefs || [], + cmdFormatter: input.cmdFormatter, + recurringTypes: input.recurringTypes || [], + initialView: input.initialView || '', + elementDraggingImpl: input.elementDraggingImpl, + optionChangeHandlers: input.optionChangeHandlers || {}, + scrollerSyncerClass: input.scrollerSyncerClass || null, + listenerRefiners: input.listenerRefiners || {}, + optionRefiners: input.optionRefiners || {}, + optionDefaults: input.optionDefaults ? [input.optionDefaults] : [], + propSetHandlers: input.propSetHandlers || {}, + }; + } + function buildPluginHooks(pluginDefs, globalDefs) { + let pluginsByName = {}; + let hooks = { + premiumReleaseDate: undefined, + reducers: [], + isLoadingFuncs: [], + contextInit: [], + eventRefiners: {}, + eventDefMemberAdders: [], + eventSourceRefiners: {}, + isDraggableTransformers: [], + eventDragMutationMassagers: [], + eventDefMutationAppliers: [], + dateSelectionTransformers: [], + datePointTransforms: [], + dateSpanTransforms: [], + views: {}, + viewPropsTransformers: [], + isPropsValid: null, + externalDefTransforms: [], + viewContainerAppends: [], + eventDropTransformers: [], + componentInteractions: [], + calendarInteractions: [], + eventSourceDefs: [], + cmdFormatter: null, + recurringTypes: [], + initialView: '', + elementDraggingImpl: null, + optionChangeHandlers: {}, + scrollerSyncerClass: null, + listenerRefiners: {}, + optionRefiners: {}, + optionDefaults: [], + propSetHandlers: {}, + }; + /* + IDs/names, etc + */ + function addDefs(defs) { + for (let unrefinedDef of defs) { + const { name } = unrefinedDef; + if (!name) { + throw new Error('Plugin must specify a name'); + } + if (!pluginsByName[name]) { + const def = pluginsByName[name] = refinePluginDef(unrefinedDef); + hooks = combineHooks(hooks, def); + addDefs(unrefinedDef.deps || []); + } + } + } + if (pluginDefs) { // how could this be undefined? + addDefs(pluginDefs); + } + addDefs(globalDefs); // GLOBAL plugins + return hooks; + } + function buildBuildPluginHooks() { + let currentOverrideDefs = []; + let currentGlobalDefs = []; + let currentHooks; + return (overrideDefs, globalDefs) => { + if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) { + currentHooks = buildPluginHooks(overrideDefs, globalDefs); + } + currentOverrideDefs = overrideDefs; + currentGlobalDefs = globalDefs; + return currentHooks; + }; + } + function combineHooks(hooks0, hooks1) { + return { + premiumReleaseDate: compareOptionalDates(hooks0.premiumReleaseDate, hooks1.premiumReleaseDate), + reducers: hooks0.reducers.concat(hooks1.reducers), + isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs), + contextInit: hooks0.contextInit.concat(hooks1.contextInit), + eventRefiners: { ...hooks0.eventRefiners, ...hooks1.eventRefiners }, + eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders), + eventSourceRefiners: { ...hooks0.eventSourceRefiners, ...hooks1.eventSourceRefiners }, + isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers), + eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers), + eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers), + dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers), + datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms), + dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms), + views: mergeViewOptionsMap(hooks0.views, hooks1.views), + viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers), + isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid, + externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms), + viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends), + eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers), + calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions), + componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions), + eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs), + cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter, + recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes), + initialView: hooks0.initialView || hooks1.initialView, // put earlier plugins FIRST + elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl, // " + optionChangeHandlers: { ...hooks0.optionChangeHandlers, ...hooks1.optionChangeHandlers }, + scrollerSyncerClass: hooks0.scrollerSyncerClass || hooks1.scrollerSyncerClass, + listenerRefiners: { ...hooks0.listenerRefiners, ...hooks1.listenerRefiners }, + optionRefiners: { ...hooks0.optionRefiners, ...hooks1.optionRefiners }, + optionDefaults: hooks0.optionDefaults.concat(hooks1.optionDefaults), + propSetHandlers: { ...hooks0.propSetHandlers, ...hooks1.propSetHandlers }, + }; + } + function compareOptionalDates(date0, date1) { + if (date0 === undefined) { + return date1; + } + if (date1 === undefined) { + return date0; + } + return new Date(Math.max(date0.valueOf(), date1.valueOf())); + } + + function compileViewDefs(defaultConfigs, overrideConfigs) { + let hash = {}; + let viewType; + for (viewType in defaultConfigs) { + ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); + } + for (viewType in overrideConfigs) { + ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); + } + return hash; + } + function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) { + if (hash[viewType]) { + return hash[viewType]; + } + let viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs); + if (viewDef) { + hash[viewType] = viewDef; + } + return viewDef; + } + function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) { + let defaultConfig = defaultConfigs[viewType]; + let overrideConfig = overrideConfigs[viewType]; + let queryProp = (name) => ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] : + ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null)); + let theComponent = queryProp('component'); + let superType = queryProp('superType'); + let superDef = null; + if (superType) { + if (superType === viewType) { + throw new Error('Can\'t have a custom view type that references itself'); + } + superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs); + } + if (!theComponent && superDef) { + theComponent = superDef.component; + } + if (!theComponent) { + return null; // don't throw a warning, might be settings for a single-unit view + } + return { + type: viewType, + component: theComponent, + defaults: mergeCalendarOptions(superDef ? superDef.defaults : {}, defaultConfig ? defaultConfig.rawOptions : {}), + overrides: mergeCalendarOptions(superDef ? superDef.overrides : {}, overrideConfig ? overrideConfig.rawOptions : {}), + }; + } + + function parseViewConfigs(inputs) { + return mapHash(inputs, parseViewConfig); + } + function parseViewConfig(input) { + let rawOptions = typeof input === 'function' ? + { component: input } : + input; + let { component } = rawOptions; + if (rawOptions.content) { + component = createViewHookComponent(rawOptions.content); + } + else if (component && !(component.prototype instanceof BaseComponent)) { + // WHY?: people were using `component` property for `content` + // TODO: converge on one setting name + component = createViewHookComponent(component); + } + return { + superType: rawOptions.type, + component: component, + rawOptions, // includes type and component too :( + }; + } + /* + TODO: converge with ViewContainer + */ + function createViewHookComponent(contentGenerator) { + return (viewProps) => (u$1(ViewContextType.Consumer, { children: (context) => { + const { options, viewSpec } = context; + const renderProps = { + // the "extra" props, for sliceEvents... + ...viewProps, + nextDayThreshold: options.nextDayThreshold, + // ViewDisplayInfo... + ...computeViewBorderless(options), + options: { headerToolbar: options.headerToolbar, footerToolbar: options.footerToolbar }, + isHeightAuto: getIsHeightAuto(options), + view: context.viewApi, + }; + return (u$1(ContentContainer, { tag: "div", className: joinClassNames(generateClassName(options.viewClass, renderProps), + // WORKAROUND for way calendar's className would get merged into view's className + generateClassName(viewSpec.optionDefaults.class, renderProps), generateClassName(viewSpec.optionDefaults.className, renderProps), generateClassName(viewSpec.optionOverrides.class, renderProps), generateClassName(viewSpec.optionOverrides.className, renderProps)), renderProps: renderProps, generatorName: undefined, customGenerator: contentGenerator, didMount: options.didMount || options.viewDidMount, willUnmount: options.willUnmount || options.viewWillUnmount })); + } })); + } + + function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides) { + let defaultConfigs = parseViewConfigs(defaultInputs); + let overrideConfigs = parseViewConfigs(optionOverrides.views); + let viewDefs = compileViewDefs(defaultConfigs, overrideConfigs); + return mapHash(viewDefs, (viewDef) => buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides)); + } + function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides) { + let durationInput = viewDef.overrides.duration || + viewDef.defaults.duration || + dynamicOptionOverrides.duration || + optionOverrides.duration; + let duration = null; + let durationUnit = ''; + let singleUnit = ''; + let singleUnitOverrides = {}; + if (durationInput) { + duration = createDurationCached(durationInput); + if (duration) { // valid? + let denom = greatestDurationDenominator(duration); + durationUnit = denom.unit; + if (denom.value === 1) { + singleUnit = durationUnit; + singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {}; + } + } + } + return { + type: viewDef.type, + component: viewDef.component, + duration, + durationUnit, + singleUnit, + optionDefaults: viewDef.defaults, + optionOverrides: { ...singleUnitOverrides, ...viewDef.overrides }, + }; + } + // hack to get memoization working + let durationInputMap = {}; + function createDurationCached(durationInput) { + let json = JSON.stringify(durationInput); + let res = durationInputMap[json]; + if (res === undefined) { + res = createDuration(durationInput); + durationInputMap[json] = res; + } + return res; + } + + function reduceViewType(viewType, action) { + switch (action.type) { + case 'CHANGE_VIEW_TYPE': + viewType = action.viewType; + } + return viewType; + } + + function reduceCurrentDate(currentDate, action) { + switch (action.type) { + case 'CHANGE_DATE': + return action.dateMarker; + default: + return currentDate; + } + } + // should be initialized once and stay constant + // this will change too + function getInitialDate(options, dateEnv, nowManager) { + let initialDateInput = options.initialDate; + // compute the initial ambig-timezone date + if (initialDateInput != null) { + return dateEnv.createMarker(initialDateInput); + } + return nowManager.getDateMarker(); + } + + function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) { + switch (action.type) { + case 'SET_OPTION': + return { ...dynamicOptionOverrides, [action.optionName]: action.rawOptionValue }; + default: + return dynamicOptionOverrides; + } + } + + function reduceDateProfile(currentDateProfile, action, currentDate, nowDate, dateProfileGenerator) { + let dp; + switch (action.type) { + case 'CHANGE_VIEW_TYPE': + return dateProfileGenerator.build(action.dateMarker || currentDate, nowDate); + case 'CHANGE_DATE': + return dateProfileGenerator.build(action.dateMarker, nowDate); + case 'PREV': + dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate, nowDate); + if (dp.isValid) { + return dp; + } + break; + case 'NEXT': + dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate, nowDate); + if (dp.isValid) { + return dp; + } + break; + } + return currentDateProfile; + } + + function reduceDateSelection(currentSelection, action) { + switch (action.type) { + case 'UNSELECT_DATES': + return null; + case 'SELECT_DATES': + return action.selection; + default: + return currentSelection; + } + } + + function reduceSelectedEvent(currentInstanceId, action) { + switch (action.type) { + case 'UNSELECT_EVENT': + return ''; + case 'SELECT_EVENT': + return action.eventInstanceId; + default: + return currentInstanceId; + } + } + + function reduceEventDrag(currentDrag, action) { + let newDrag; + switch (action.type) { + case 'UNSET_EVENT_DRAG': + return null; + case 'SET_EVENT_DRAG': + newDrag = action.state; + return { + affectedEvents: newDrag.affectedEvents, + mutatedEvents: newDrag.mutatedEvents, + isEvent: newDrag.isEvent, + }; + default: + return currentDrag; + } + } + + function reduceEventResize(currentResize, action) { + let newResize; + switch (action.type) { + case 'UNSET_EVENT_RESIZE': + return null; + case 'SET_EVENT_RESIZE': + newResize = action.state; + return { + affectedEvents: newResize.affectedEvents, + mutatedEvents: newResize.mutatedEvents, + isEvent: newResize.isEvent, + }; + default: + return currentResize; + } + } + + function parseToolbars(calendarOptions, viewSpecs, calendarApi) { + let header = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, viewSpecs, calendarApi) : null; + let footer = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, viewSpecs, calendarApi) : null; + return { header, footer }; + } + function parseToolbar(sectionStrHash, calendarOptions, viewSpecs, calendarApi) { + let isRtl = calendarOptions.direction === 'rtl'; + let viewsWithButtons = []; + let hasTitle = false; + function processSectionStr(sectionStr) { + let sectionRes = parseSection(sectionStr, calendarOptions, viewSpecs, calendarApi); + viewsWithButtons.push(...sectionRes.viewsWithButtons); + hasTitle = hasTitle || sectionRes.hasTitle; + return sectionRes.widgets; + } + const sectionWidgets = { + start: processSectionStr(sectionStrHash[isRtl ? 'right' : 'left'] || sectionStrHash.start || ''), + center: processSectionStr(sectionStrHash.center || ''), + end: processSectionStr(sectionStrHash[isRtl ? 'left' : 'right'] || sectionStrHash.end || ''), + }; + return { + sectionWidgets, + viewsWithButtons, + hasTitle, + }; + } + /* + BAD: querying icons and text here. should be done at render time + */ + function parseSection(sectionStr, calendarOptions, viewSpecs, calendarApi) { + let calendarButtons = calendarOptions.buttons || {}; + let customElements = calendarOptions.toolbarElements || {}; + let sectionSubstrs = sectionStr ? sectionStr.split(' ') : []; + let viewsWithButtons = []; + let hasTitle = false; + let widgets = sectionSubstrs.map((buttonGroupStr) => (buttonGroupStr.split(',').map((name) => { + if (name === 'title') { + hasTitle = true; + return { name }; + } + if (customElements[name]) { + return { name, customElement: customElements[name] }; + } + let viewSpec; + let buttonInput = calendarButtons[name] || {}; + let buttonText; + let buttonHint; + let buttonClick; + if ((viewSpec = viewSpecs[name])) { + viewsWithButtons.push(name); + const buttonTextKey = viewSpec.optionDefaults.buttonTextKey; + buttonText = buttonInput.text || + (buttonTextKey ? calendarOptions[buttonTextKey] : '') || + (viewSpec.singleUnit + ? (calendarOptions[viewSpec.singleUnit + 'TextLong'] || + calendarOptions[viewSpec.singleUnit + 'Text']) + : '') || + name; + /* + buttons{}.hint(viewButtonText, viewName) + viewHint(viewButtonText, viewName) + */ + buttonHint = formatWithOrdinals(buttonInput.hint || calendarOptions.viewHint, [buttonText, name], // ordinal arguments + buttonText); + buttonClick = (ev) => { + buttonInput?.click?.(ev); + if (!ev.defaultPrevented) { + calendarApi.changeView(name); + } + }; + } + else { + buttonText = buttonInput.text || + calendarOptions[name + 'TextLong'] || + calendarOptions[name + 'Text'] || + name; + /* + buttons{}.hint(currentUnitText, currentUnit) + prevHint(currentUnitUnitext, currentUnit) + nextHint -- same + todayHint -- same + */ + if (name === 'prevYear') { + buttonHint = formatWithOrdinals(buttonInput.hint || calendarOptions.prevHint, [calendarOptions.yearText, 'year'], buttonText); + } + else if (name === 'nextYear') { + buttonHint = formatWithOrdinals(buttonInput.hint || calendarOptions.nextHint, [calendarOptions.yearText, 'year'], buttonText); + } + else { + buttonHint = (currentUnit) => { + return formatWithOrdinals(buttonInput.hint || calendarOptions[name + 'Hint'], // todayHint/prevHint/nextHint + [ + calendarOptions[currentUnit + 'TextLong'] || + calendarOptions[currentUnit + 'Text'], + currentUnit + ], buttonText); + }; + } + buttonClick = (ev) => { + buttonInput?.click?.(ev); + if (!ev.defaultPrevented) { + calendarApi[name]?.(); + } + }; + } + return { + name, + isView: Boolean(viewSpec), + buttonText, + buttonHint, + buttonDisplay: buttonInput.display, + buttonIconClass: buttonInput.iconClass, + buttonIconContent: buttonInput.iconContent, + buttonClick, + buttonIsPrimary: buttonInput.isPrimary || false, + buttonClass: buttonInput.class ?? buttonInput.className, + buttonDidMount: buttonInput.didMount, + buttonWillUnmount: buttonInput.willUnmount, + }; + }))); + return { widgets, viewsWithButtons, hasTitle }; + } + + // always represents the current view. otherwise, it'd need to change value every time date changes + class ViewImpl { + constructor(type, getCurrentData, dateEnv) { + this.type = type; + this.getCurrentData = getCurrentData; + this.dateEnv = dateEnv; + } + get calendar() { + return this.getCurrentData().calendarApi; + } + get title() { + return this.getCurrentData().viewTitle; + } + get activeStart() { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start); + } + get activeEnd() { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end); + } + get currentStart() { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start); + } + get currentEnd() { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end); + } + getOption(name) { + return this.getCurrentData().options[name]; // are the view-specific options + } + } + + const DEF_DEFAULTS = { + startTime: '09:00', + endTime: '17:00', + daysOfWeek: [1, 2, 3, 4, 5], // monday - friday + display: 'inverse-background', + className: '', // TODO: remove + groupId: '_businessHours', // so multiple defs get grouped + }; + /* + TODO: pass around as EventDefHash!!! + */ + function parseBusinessHours(input, context) { + return parseEvents(refineInputs(input), null, context); + } + function refineInputs(input) { + let rawDefs; + if (input === true) { + rawDefs = [{}]; // will get DEF_DEFAULTS verbatim + } + else if (Array.isArray(input)) { + // if specifying an array, every sub-definition NEEDS a day-of-week + rawDefs = input.filter((rawDef) => rawDef.daysOfWeek); + } + else if (typeof input === 'object' && input) { // non-null object + rawDefs = [input]; + } + else { // is probably false + rawDefs = []; + } + rawDefs = rawDefs.map((rawDef) => ({ ...DEF_DEFAULTS, ...rawDef })); + return rawDefs; + } + + // Computes what the title at the top of the calendarApi should be for this view + function buildTitle(dateProfile, viewOptions, dateEnv) { + let range; + // for views that span a large unit of time, show the proper interval, ignoring stray days before and after + if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) { + range = dateProfile.currentRange; + } + else { // for day units or smaller, use the actual day range + range = dateProfile.activeRange; + } + let parts; + const options = { isEndExclusive: dateProfile.isRangeAllDay }; + if (viewOptions.titleFormat) { + parts = dateEnv.formatRangeToParts(range.start, range.end, createFormatter(viewOptions.titleFormat), options); + } + else { + parts = dateEnv.formatRangeToParts(range.start, range.end, createFormatter(buildTitleFormat(dateProfile, viewOptions.disallowAmbigTitle, 'long')), options); + if (hasTwoMonths(parts)) { + parts = dateEnv.formatRangeToParts(range.start, range.end, createFormatter(buildTitleFormat(dateProfile, viewOptions.disallowAmbigTitle, 'short')), options); + } + } + return joinDateTimeFormatParts(parts); + } + // Generates the format string that should be used to generate the title for the current date range. + // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`. + function buildTitleFormat(dateProfile, disallowAmbigTitle, monthFormat) { + const { currentRangeUnit } = dateProfile; + if (currentRangeUnit === 'year') { + return { year: 'numeric' }; + } + if (currentRangeUnit === 'month') { + return { year: 'numeric', month: monthFormat }; + } + if (!disallowAmbigTitle) { + const days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end); + if (days !== null && days > 1) { + return { + year: 'numeric', + month: monthFormat, + }; + } + } + // one day. longer, like "September 9 2014" + return { year: 'numeric', month: 'long', day: 'numeric' }; + } + function hasTwoMonths(parts) { + let hasStartMonth = false; + let hasEndMonth = false; + for (const part of parts) { + if (part.type === 'month') { + if (part.source === 'startRange') + hasStartMonth = true; + if (part.source === 'endRange') + hasEndMonth = true; + } + } + return hasStartMonth && hasEndMonth; + } + + /* + TODO: test switching timezones when NO timezone plugin + */ + class CalendarNowManager { + constructor() { + this.resetListeners = new Set(); + } + handleInput(dateEnv, // will change if timezone setup changed + nowInput) { + const oldDateEnv = this.dateEnv; + if (dateEnv !== oldDateEnv) { + if (typeof nowInput === 'function') { + this.nowFn = nowInput; + } + else if (!oldDateEnv) { // first time? + this.nowAnchorDate = dateEnv.toDate(nowInput + ? dateEnv.createMarker(nowInput) + : dateEnv.createNowMarker()); + this.nowAnchorQueried = Date.now(); + } + this.dateEnv = dateEnv; + // not first time? fire reset handlers + if (oldDateEnv) { + for (const resetListener of this.resetListeners.values()) { + resetListener(); + } + } + } + } + getDateMarker() { + return this.nowAnchorDate + ? this.dateEnv.timestampToMarker(this.nowAnchorDate.valueOf() + + (Date.now() - this.nowAnchorQueried)) + : this.dateEnv.createMarker(this.nowFn()); + } + addResetListener(handler) { + this.resetListeners.add(handler); + } + removeResetListener(handler) { + this.resetListeners.delete(handler); + } + } + + class CalendarDataManager { + constructor(config) { + this.computeCurrentViewData = memoize(this._computeCurrentViewData); + this.organizeRawLocales = memoize(organizeRawLocales); + this.buildLocale = memoize(buildLocale); + this.buildPluginHooks = buildBuildPluginHooks(); + this.buildDateEnv = memoize(buildDateEnv$1); + this.parseToolbars = memoize(parseToolbars); + this.buildViewSpecs = memoize(buildViewSpecs); + this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator); + this.buildViewApi = memoize(buildViewApi); + this.buildViewUiProps = memoizeObjArg(buildViewUiProps); + this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqualShallow); + this.buildEventUiBases = memoize(buildEventUiBases); + this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours); + this.buildToolbarProps = memoize(buildToolbarProps); + this.buildTitle = memoize(buildTitle); + this.nowManager = new CalendarNowManager(); + this.isDrainingActionQueue = false; + this.actionQueue = []; + this.optionOverrides = {}; + // used by CalendarApiImpl + this.emitter = new Emitter(); + this.currentCalendarOptionsRefiners = {}; + this.currentCalendarOptionsInput = {}; + this.currentCalendarOptionsRefined = {}; + this.currentViewOptionsInput = {}; + this.currentViewOptionsRefined = {}; + this.optionsForRefining = []; + this.optionsForHandling = []; + this.getCurrentData = () => this.data; + this.handleNowChange = () => { + this.dispatch({ type: 'UPDATE_NOW' }); + }; + this.dispatch = (action) => { + this.actionQueue.push(action); + if (!this.isDrainingActionQueue) { + this.drainActionQueue(); + } + }; + this.config = config; + this.nowManager = new CalendarNowManager(); + this.nowTimer = new NowTimerRunner(this.handleNowChange); + } + destroy() { + this.nowTimer.destroy(); + } + /* + Will NOT trigger onDataChange unless there were other actions in the queue + */ + update(optionOverrides) { + this.optionOverrides = optionOverrides; + this.actionQueue.push({ type: 'IDLE' }); // ensure reducer gets called + this.drainActionQueue(); + return this.data; + } + /* + WILL trigger onDataChange + */ + resetOptions(optionOverrides, changedOptionNames) { + if (changedOptionNames === undefined) { + this.optionOverrides = optionOverrides; + } + else { + this.optionOverrides = { ...this.optionOverrides, ...optionOverrides }; + this.optionsForRefining.push(...changedOptionNames); + } + this.dispatch({ type: 'RESET_OPTIONS' }); + } + drainActionQueue() { + let calendarContext; + let { state, data } = this; + const isInit = !state; + const { actionQueue } = this; + const actionsComplete = []; // non-idle + this.isDrainingActionQueue = true; + while (actionQueue.length) { + const action = actionQueue.shift(); + ({ state, data, calendarContext } = this.reduce(state, data, action)); + this.state = state; + this.data = data; + if (action.type !== 'IDLE') { + actionsComplete.push(action); + } + } + this.isDrainingActionQueue = false; + if (isInit) { + const controllerOption = calendarContext.options.controller; + if (controllerOption) { + controllerOption._setApi(this.config.calendarApi); + } + } + if (!isInit && actionsComplete.length) { + const { onDataChange } = this.config; + if (onDataChange) { + onDataChange(this.data, actionsComplete); + } + } + } + reduce(prevState, prevData, action) { + let { config } = this; + let isInit = !prevState; + // === Compute options and view data === + let dynamicOptionOverrides = isInit + ? {} + : reduceDynamicOptionOverrides(prevState.dynamicOptionOverrides, action); + let optionsData = this.computeOptionsData(this.optionOverrides, dynamicOptionOverrides, config.calendarApi); + let currentViewType = isInit + ? (optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView) + : reduceViewType(prevState.currentViewType, action); + let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, this.optionOverrides, dynamicOptionOverrides); + // === Wire things up === + config.calendarApi.currentDataManager = this; + this.emitter.setThisContext(config.calendarApi); + this.emitter.setOptions(currentViewData.options); + // === Build calendarContext === + let calendarContext = { + nowManager: this.nowManager, + dateEnv: optionsData.dateEnv, + options: optionsData.calendarOptions, + pluginHooks: optionsData.pluginHooks, + calendarApi: config.calendarApi, + dispatch: this.dispatch, + emitter: this.emitter, + getCurrentData: this.getCurrentData, + }; + // === Update now timer === + let { nowDate } = this.nowTimer.update({ + unit: 'day', + unitValue: 1, + nowIndicatorSnap: 'auto', + nowManager: this.nowManager, + dateEnv: optionsData.dateEnv, + }); + // === Compute currentDate === + let currentDate = isInit + ? getInitialDate(optionsData.calendarOptions, optionsData.dateEnv, this.nowManager) + : reduceCurrentDate(prevState.currentDate, action); + // === Compute dateProfile === + let dateProfile; + if (isInit) { + dateProfile = currentViewData.dateProfileGenerator.build(currentDate, nowDate); + } + else { + dateProfile = prevState.dateProfile; + // Check for generator change + if (prevData && prevData.dateProfileGenerator !== currentViewData.dateProfileGenerator) { + dateProfile = currentViewData.dateProfileGenerator.build(currentDate, nowDate); + } + dateProfile = reduceDateProfile(dateProfile, action, currentDate, nowDate, currentViewData.dateProfileGenerator); + } + // === Adjust currentDate if out of range === + if ((action && (action.type === 'PREV' || action.type === 'NEXT')) || + !rangeContainsMarker(dateProfile.activeRange, currentDate)) { + currentDate = dateProfile.currentRange.start; + } + // === Compute eventSources, eventStore === + let eventSources = isInit + ? initEventSources(optionsData.calendarOptions, dateProfile, calendarContext) + : reduceEventSources(prevState.eventSources, action, dateProfile, calendarContext); + let eventStore = isInit + ? createEmptyEventStore() + : reduceEventStore(prevState.eventStore, action, eventSources, dateProfile, calendarContext); + // === Compute renderableEventStore === + let isEventsLoading = computeEventSourcesLoading(eventSources); + let renderableEventStore = isInit + ? createEmptyEventStore() + : (isEventsLoading && !currentViewData.options.progressiveEventRendering) + ? (prevState.renderableEventStore || eventStore) + : eventStore; + // === UI computation === + let { eventUiSingleBase, selectionConfig } = this.buildViewUiProps(calendarContext); + let eventUiBySource = this.buildEventUiBySource(eventSources); + let eventUiBases = isInit + ? {} + : this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource); + // === Build new state === + let newState = { + dynamicOptionOverrides, + currentViewType, + currentDate, + dateProfile, + eventSources, + eventStore, + renderableEventStore, + selectionConfig, + eventUiBases, + businessHours: this.parseContextBusinessHours(calendarContext), + dateSelection: isInit ? null : reduceDateSelection(prevState.dateSelection, action), + eventSelection: isInit ? '' : reduceSelectedEvent(prevState.eventSelection, action), + eventDrag: isInit ? null : reduceEventDrag(prevState.eventDrag, action), + eventResize: isInit ? null : reduceEventResize(prevState.eventResize, action), + nowDate, + }; + // === Plugin reducers === + let contextAndState = { ...calendarContext, ...newState }; + for (let reducer of optionsData.pluginHooks.reducers) { + Object.assign(newState, reducer(prevState, action, contextAndState)); + } + // === Loading state emission === + let wasLoading = prevState ? computeIsLoading(prevState, calendarContext) : false; + let isLoading = computeIsLoading(newState, calendarContext); + if (!wasLoading && isLoading) { + this.emitter.trigger('loading', true); + } + else if (wasLoading && !isLoading) { + this.emitter.trigger('loading', false); + } + // === Build CalendarData === + let viewTitle = this.buildTitle(dateProfile, currentViewData.options, optionsData.dateEnv); + let toolbarProps = this.buildToolbarProps(currentViewData.viewSpec, dateProfile, currentViewData.dateProfileGenerator, currentDate, nowDate, viewTitle); + let newData = { + viewTitle, + nowManager: this.nowManager, + calendarApi: config.calendarApi, + dispatch: this.dispatch, + emitter: this.emitter, + getCurrentData: this.getCurrentData, + toolbarProps, + ...optionsData, + ...currentViewData, + ...newState, + }; + // === Handle option changes === + let changeHandlers = optionsData.pluginHooks.optionChangeHandlers; + let prevCalendarOptions = prevData && prevData.calendarOptions; + let newCalendarOptions = optionsData.calendarOptions; + if (prevCalendarOptions && prevCalendarOptions !== newCalendarOptions) { + if (prevCalendarOptions.timeZone !== newCalendarOptions.timeZone) { + // HACK + newState.eventSources = newData.eventSources = reduceEventSourcesNewTimeZone(newData.eventSources, dateProfile, newData); + newState.eventStore = newData.eventStore = rezoneEventStoreDates(newData.eventStore, prevData.dateEnv, newData.dateEnv); + newState.renderableEventStore = newData.renderableEventStore = rezoneEventStoreDates(newData.renderableEventStore, prevData.dateEnv, newData.dateEnv); + } + for (let optionName in changeHandlers) { + if (this.optionsForHandling.indexOf(optionName) !== -1 || + prevCalendarOptions[optionName] !== newCalendarOptions[optionName]) { + changeHandlers[optionName](newCalendarOptions[optionName], newData); + } + } + } + this.optionsForHandling = []; + return { state: newState, data: newData, calendarContext }; + } + computeOptionsData(optionOverrides, dynamicOptionOverrides, calendarApi) { + // TODO: blacklist options that are handled by optionChangeHandlers + if (!this.optionsForRefining.length && + optionOverrides === this.stableOptionOverrides && + dynamicOptionOverrides === this.stableDynamicOptionOverrides) { + return this.stableCalendarOptionsData; + } + let { refinedOptions, pluginHooks, localeDefaults, availableLocaleData, } = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides); + let dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekTextLong, refinedOptions.weekTextShort, pluginHooks, availableLocaleData); + let viewSpecs = this.buildViewSpecs(pluginHooks.views, this.stableOptionOverrides, this.stableDynamicOptionOverrides); + let toolbarConfig = this.parseToolbars(refinedOptions, viewSpecs, calendarApi); + return this.stableCalendarOptionsData = { + calendarOptions: refinedOptions, + pluginHooks, + dateEnv, + viewSpecs, + toolbarConfig, + localeDefaults, + availableRawLocales: availableLocaleData.map, + }; + } + // always called from behind a memoizer + processRawCalendarOptions(optionOverrides, dynamicOptionOverrides) { + let { locales, locale } = mergeCalendarOptions(BASE_OPTION_DEFAULTS, optionOverrides, dynamicOptionOverrides); + let availableLocaleData = this.organizeRawLocales(locales); + let availableRawLocales = availableLocaleData.map; + let localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options; + let pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins); + let refiners = this.currentCalendarOptionsRefiners = { + ...BASE_OPTION_REFINERS, + ...CALENDAR_LISTENER_REFINERS, + ...CALENDAR_ONLY_OPTION_REFINERS, + ...pluginHooks.listenerRefiners, + ...pluginHooks.optionRefiners, + }; + let raw = mergeCalendarOptions(BASE_OPTION_DEFAULTS, ...pluginHooks.optionDefaults, localeDefaults, filterKnownOptions(mergeCalendarOptions(optionOverrides, dynamicOptionOverrides), refiners)); + let refined = {}; + let currentRaw = this.currentCalendarOptionsInput; + let currentRefined = this.currentCalendarOptionsRefined; + let anyChanges = false; + for (let optionName in raw) { + if (this.optionsForRefining.indexOf(optionName) === -1 && (raw[optionName] === currentRaw[optionName] || (COMPLEX_OPTION_COMPARATORS[optionName] && + (optionName in currentRaw) && + COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName])) || isMergedPropsEqual(currentRaw[optionName], raw[optionName]))) { + refined[optionName] = currentRefined[optionName]; + } + else if (refiners[optionName]) { + refined[optionName] = refiners[optionName](raw[optionName], optionName); + anyChanges = true; + } + } + if (anyChanges) { + this.currentCalendarOptionsInput = raw; + this.currentCalendarOptionsRefined = refined; + this.stableOptionOverrides = optionOverrides; + this.stableDynamicOptionOverrides = dynamicOptionOverrides; + } + this.optionsForHandling.push(...this.optionsForRefining); + this.optionsForRefining = []; + return { + rawOptions: this.currentCalendarOptionsInput, + refinedOptions: this.currentCalendarOptionsRefined, + pluginHooks, + availableLocaleData, + localeDefaults, + }; + } + _computeCurrentViewData(viewType, optionsData, optionOverrides, dynamicOptionOverrides) { + let viewSpec = optionsData.viewSpecs[viewType]; + if (!viewSpec) { + throw new Error(`viewType "${viewType}" is not available. Please make sure you've loaded all neccessary plugins`); + } + let { refinedOptions } = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides); + this.nowManager.handleInput(optionsData.dateEnv, refinedOptions.now); + let dateProfileGenerator = this.buildDateProfileGenerator({ + dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass, + duration: viewSpec.duration, + durationUnit: viewSpec.durationUnit, + usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime, + dateEnv: optionsData.dateEnv, + calendarApi: this.config.calendarApi, + slotMinTime: refinedOptions.slotMinTime, + slotMaxTime: refinedOptions.slotMaxTime, + showNonCurrentDates: refinedOptions.showNonCurrentDates, + dayCount: refinedOptions.dayCount, + dateAlignment: refinedOptions.dateAlignment, + dateIncrement: refinedOptions.dateIncrement, + hiddenDays: refinedOptions.hiddenDays, + weekends: refinedOptions.weekends, + validRangeInput: refinedOptions.validRange, + visibleRangeInput: refinedOptions.visibleRange, + fixedWeekCount: refinedOptions.fixedWeekCount, + }); + let viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv); + return { viewSpec, options: refinedOptions, dateProfileGenerator, viewApi }; + } + processRawViewOptions(viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) { + let refiners = { + ...BASE_OPTION_REFINERS, + ...CALENDAR_LISTENER_REFINERS, + ...CALENDAR_ONLY_OPTION_REFINERS, + ...VIEW_ONLY_OPTION_REFINERS, + ...pluginHooks.listenerRefiners, + ...pluginHooks.optionRefiners, + }; + let raw = mergeCalendarOptions(BASE_OPTION_DEFAULTS, ...pluginHooks.optionDefaults, viewSpec.optionDefaults, localeDefaults, filterKnownOptions(mergeCalendarOptions(optionOverrides, viewSpec.optionOverrides, dynamicOptionOverrides), refiners)); + let refined = {}; + let currentRaw = this.currentViewOptionsInput; + let currentRefined = this.currentViewOptionsRefined; + let anyChanges = false; + for (let optionName in raw) { + if (raw[optionName] === currentRaw[optionName] || (COMPLEX_OPTION_COMPARATORS[optionName] && + COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], currentRaw[optionName])) || isMergedPropsEqual(currentRaw[optionName], raw[optionName])) { + refined[optionName] = currentRefined[optionName]; + } + else { + if (raw[optionName] === this.currentCalendarOptionsInput[optionName] || + (COMPLEX_OPTION_COMPARATORS[optionName] && + COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], this.currentCalendarOptionsInput[optionName]))) { + if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop + refined[optionName] = this.currentCalendarOptionsRefined[optionName]; + } + } + else if (refiners[optionName]) { + refined[optionName] = refiners[optionName](raw[optionName], optionName); + } + anyChanges = true; + } + } + if (anyChanges) { + this.currentViewOptionsInput = raw; + this.currentViewOptionsRefined = refined; + } + return { + rawOptions: this.currentViewOptionsInput, + refinedOptions: this.currentViewOptionsRefined, + }; + } + } + function buildDateEnv$1(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekTextLong, weekTextShort, pluginHooks, availableLocaleData) { + let locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map); + return new DateEnv({ + calendarSystem: 'gregory', // TODO: make this a setting + timeZone, + locale, + weekNumberCalculation, + firstDay, + weekTextLong, + weekTextShort, + cmdFormatter: pluginHooks.cmdFormatter, + }); + } + function buildDateProfileGenerator(props) { + let DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator; + return new DateProfileGeneratorClass(props); + } + function buildViewApi(type, getCurrentData, dateEnv) { + return new ViewImpl(type, getCurrentData, dateEnv); + } + function buildEventUiBySource(eventSources) { + return mapHash(eventSources, (eventSource) => eventSource.ui); + } + /* + The result of this is processed by compileEventUi + */ + function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) { + let eventUiBases = { + '': eventUiSingleBase, // fallback + }; + for (let defId in eventDefs) { + let def = eventDefs[defId]; + if (def.sourceId && eventUiBySource[def.sourceId]) { + eventUiBases[defId] = eventUiBySource[def.sourceId]; + } + } + return eventUiBases; + } + function buildViewUiProps(calendarContext) { + const { options } = calendarContext; + return { + eventUiSingleBase: createEventUi({ + display: options.eventDisplay, + editable: options.editable, // without "event" at start + startEditable: options.eventStartEditable, + durationEditable: options.eventDurationEditable, + constraint: options.eventConstraint, + overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined, + allow: options.eventAllow, + // color: options.eventColor, // StandardEvent/BgEvent will handle this + // contrastColor: options.eventContrastColor, // StandardEvent/BgEvent will handle this + // className: options.eventClass // render hook will handle this + }, calendarContext), + selectionConfig: createEventUi({ + constraint: options.selectConstraint, + overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined, + allow: options.selectAllow, + }, calendarContext), + }; + } + function computeIsLoading(state, context) { + for (let isLoadingFunc of context.pluginHooks.isLoadingFuncs) { + if (isLoadingFunc(state)) { + return true; + } + } + return false; + } + function parseContextBusinessHours(calendarContext) { + return parseBusinessHours(calendarContext.options.businessHours, calendarContext); + } + const warnedUnknownOptions = {}; + function filterKnownOptions(options, optionRefiners) { + const knownOptions = {}; + for (const optionName in options) { + if (optionRefiners[optionName]) { + knownOptions[optionName] = options[optionName]; + } + else if (!warnedUnknownOptions[optionName]) { + warn(`Unknown option \`${optionName}\`.`); + warnedUnknownOptions[optionName] = true; + } + } + return knownOptions; + } + function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, nowDate, title) { + // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid + let todayInfo = dateProfileGenerator.build(nowDate, nowDate, undefined, /* forceToValid = */ false); + let prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, nowDate, /* forceToValid = */ false); + let nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, nowDate, /* forceToValid = */ false); + return { + title, + selectedButton: viewSpec.type, + navUnit: viewSpec.singleUnit, + isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, nowDate), + isPrevEnabled: prevInfo.isValid, + isNextEnabled: nextInfo.isValid, + }; + } + + class CalendarApiImpl { + getCurrentData() { + return this.currentDataManager.getCurrentData(); + } + dispatch(action) { + this.currentDataManager.dispatch(action); + } + get view() { return this.getCurrentData().viewApi; } + batchRendering(callback) { + callback(); + } + // Options + // ----------------------------------------------------------------------------------------------------------------- + setOption(name, val) { + this.dispatch({ + type: 'SET_OPTION', + optionName: name, + rawOptionValue: val, + }); + } + getOption(name) { + return this.currentDataManager.currentCalendarOptionsInput[name]; + } + getAvailableLocaleCodes() { + return Object.keys(this.getCurrentData().availableRawLocales); + } + // Trigger + // ----------------------------------------------------------------------------------------------------------------- + on(handlerName, handler) { + let { currentDataManager } = this; + if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) { + currentDataManager.emitter.on(handlerName, handler); + } + else { + warn(`Unknown listener \`${handlerName}\`.`); + } + } + off(handlerName, handler) { + this.currentDataManager.emitter.off(handlerName, handler); + } + // not meant for public use + trigger(handlerName, ...args) { + this.currentDataManager.emitter.trigger(handlerName, ...args); + } + // View + // ----------------------------------------------------------------------------------------------------------------- + changeView(viewType, dateOrRange) { + this.batchRendering(() => { + this.unselect(); + if (dateOrRange) { + if (dateOrRange.start && dateOrRange.end) { // a range + this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType, + }); + this.dispatch({ + type: 'SET_OPTION', + optionName: 'visibleRange', + rawOptionValue: dateOrRange, + }); + } + else { + let { dateEnv } = this.getCurrentData(); + this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType, + dateMarker: dateEnv.createMarker(dateOrRange), + }); + } + } + else { + this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType, + }); + } + }); + } + // Forces navigation to a view for the given date. + // `viewType` can be a specific view name or a generic one like "week" or "day". + // needs to change + zoomTo(dateMarker, viewType) { + let state = this.getCurrentData(); + let spec; + viewType = viewType || 'day'; // day is default zoom + spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType); + this.unselect(); + if (spec) { + this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: spec.type, + dateMarker, + }); + } + else { + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker, + }); + } + } + // Given a duration singular unit, like "week" or "day", finds a matching view spec. + // Preference is given to views that have corresponding buttons. + getUnitViewSpec(unit) { + let { viewSpecs, toolbarConfig } = this.getCurrentData(); + let viewTypes = [].concat(toolbarConfig.header ? toolbarConfig.header.viewsWithButtons : [], toolbarConfig.footer ? toolbarConfig.footer.viewsWithButtons : []); + let i; + let spec; + for (let viewType in viewSpecs) { + viewTypes.push(viewType); + } + for (i = 0; i < viewTypes.length; i += 1) { + spec = viewSpecs[viewTypes[i]]; + if (spec) { + if (spec.singleUnit === unit) { + return spec; + } + } + } + return null; + } + // Current Date + // ----------------------------------------------------------------------------------------------------------------- + prev() { + this.unselect(); + this.dispatch({ type: 'PREV' }); + } + next() { + this.unselect(); + this.dispatch({ type: 'NEXT' }); + } + prevYear() { + let state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.addYears(state.currentDate, -1), + }); + } + nextYear() { + let state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.addYears(state.currentDate, 1), + }); + } + today() { + let state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.nowManager.getDateMarker(), + }); + } + gotoDate(zonedDateInput) { + let state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.createMarker(zonedDateInput), + }); + } + incrementDate(deltaInput) { + let state = this.getCurrentData(); + let delta = createDuration(deltaInput); + if (delta) { // else, warn about invalid input? + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.add(state.currentDate, delta), + }); + } + } + getDate() { + let state = this.getCurrentData(); + return state.dateEnv.toDate(state.currentDate); + } + // Date Formatting Utils + // ----------------------------------------------------------------------------------------------------------------- + formatDate(d, formatter) { + let { dateEnv } = this.getCurrentData(); + return joinDateTimeFormatParts(dateEnv.formatToParts(dateEnv.createMarker(d), createFormatter(formatter))); + } + // `settings` is for formatter AND isEndExclusive + formatRange(d0, d1, settings) { + let { dateEnv } = this.getCurrentData(); + return joinDateTimeFormatParts(dateEnv.formatRangeToParts(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings), settings)); + } + formatIso(d, omitTime) { + let { dateEnv } = this.getCurrentData(); + return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime }); + } + // Date Selection / Event Selection / DayClick + // ----------------------------------------------------------------------------------------------------------------- + select(dateOrObj, endDate) { + let selectionInput; + if (endDate == null) { + if (dateOrObj.start != null) { + selectionInput = dateOrObj; + } + else { + selectionInput = { + start: dateOrObj, + end: null, + }; + } + } + else { + selectionInput = { + start: dateOrObj, + end: endDate, + }; + } + let state = this.getCurrentData(); + let selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 })); + if (selection) { // throw parse error otherwise? + this.dispatch({ type: 'SELECT_DATES', selection }); + triggerDateSelect(selection, null, state); + } + } + unselect(pev) { + let state = this.getCurrentData(); + if (state.dateSelection) { + this.dispatch({ type: 'UNSELECT_DATES' }); + triggerDateUnselect(pev, state); + } + } + // Public Events API + // ----------------------------------------------------------------------------------------------------------------- + addEvent(eventInput, sourceInput) { + if (eventInput instanceof EventImpl) { + let def = eventInput._def; + let instance = eventInput._instance; + let currentData = this.getCurrentData(); + // not already present? don't want to add an old snapshot + if (!currentData.eventStore.defs[def.defId]) { + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore({ def, instance }), // TODO: better util for two args? + }); + this.triggerEventAdd(eventInput); + } + return eventInput; + } + let state = this.getCurrentData(); + let eventSource; + if (sourceInput instanceof EventSourceImpl) { + eventSource = sourceInput.internalEventSource; + } + else if (typeof sourceInput === 'boolean') { + if (sourceInput) { // true. part of the first event source + [eventSource] = hashValuesToArray(state.eventSources); + } + } + else if (sourceInput != null) { // an ID. accepts a number too + let sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function + if (!sourceApi) { + warn(`Unknown event source ID \`${sourceInput}\`.`); // TODO: test + return null; + } + eventSource = sourceApi.internalEventSource; + } + let tuple = parseEvent(eventInput, eventSource, state, false); + if (tuple) { + let newEventApi = new EventImpl(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance); + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore(tuple), + }); + this.triggerEventAdd(newEventApi); + return newEventApi; + } + return null; + } + triggerEventAdd(eventApi) { + let { emitter } = this.getCurrentData(); + emitter.trigger('eventAdd', { + event: eventApi, + relatedEvents: [], + revert: () => { + this.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: eventApiToStore(eventApi), + }); + }, + }); + } + // TODO: optimize + getEventById(id) { + let state = this.getCurrentData(); + let { defs, instances } = state.eventStore; + id = String(id); + for (let defId in defs) { + let def = defs[defId]; + if (def.publicId === id) { + if (def.recurringDef) { + return new EventImpl(state, def, null); + } + for (let instanceId in instances) { + let instance = instances[instanceId]; + if (instance.defId === def.defId) { + return new EventImpl(state, def, instance); + } + } + } + } + return null; + } + getEvents() { + let currentData = this.getCurrentData(); + return buildEventApis(currentData.eventStore, currentData); + } + removeAllEvents() { + this.dispatch({ type: 'REMOVE_ALL_EVENTS' }); + } + // Public Event Sources API + // ----------------------------------------------------------------------------------------------------------------- + getEventSources() { + let state = this.getCurrentData(); + let sourceHash = state.eventSources; + let sourceApis = []; + for (let internalId in sourceHash) { + sourceApis.push(new EventSourceImpl(state, sourceHash[internalId])); + } + return sourceApis; + } + getEventSourceById(id) { + let state = this.getCurrentData(); + let sourceHash = state.eventSources; + id = String(id); + for (let sourceId in sourceHash) { + if (sourceHash[sourceId].publicId === id) { + return new EventSourceImpl(state, sourceHash[sourceId]); + } + } + return null; + } + addEventSource(sourceInput) { + let state = this.getCurrentData(); + if (sourceInput instanceof EventSourceImpl) { + // not already present? don't want to add an old snapshot + if (!state.eventSources[sourceInput.internalEventSource.sourceId]) { + this.dispatch({ + type: 'ADD_EVENT_SOURCES', + sources: [sourceInput.internalEventSource], + }); + } + return sourceInput; + } + let eventSource = parseEventSource(sourceInput, state); + if (eventSource) { // TODO: error otherwise? + this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] }); + return new EventSourceImpl(state, eventSource); + } + return null; + } + removeAllEventSources() { + this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' }); + } + refetchEvents() { + this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true }); + } + // Scroll + // ----------------------------------------------------------------------------------------------------------------- + scrollToTime(timeInput) { + let time = createDuration(timeInput); + if (time) { + this.trigger('_timeScrollRequest', time); + } + } + // Button State + // ----------------------------------------------------------------------------------------------------------------- + getButtonState() { + const currentData = this.getCurrentData(); + const { toolbarProps } = currentData; + const options = currentData.calendarOptions; + const buttonConfigs = options.buttons || {}; + const viewSpecs = currentData.viewSpecs; + const currentUnit = currentData.viewSpec.singleUnit; + const currentHintOrdinal = [ + currentUnit ? getSingleUnitText(currentUnit, options) : '', + currentUnit, + ]; + const buttonState = { + today: { + text: options.todayText, + hint: formatWithOrdinals(options.todayHint, currentHintOrdinal, options.todayText), + isDisabled: !toolbarProps.isTodayEnabled, + }, + prev: { + text: options.prevText, + hint: formatWithOrdinals(options.prevHint, currentHintOrdinal, options.prevText), + isDisabled: !toolbarProps.isPrevEnabled, + }, + next: { + text: options.nextText, + hint: formatWithOrdinals(options.nextHint, currentHintOrdinal, options.nextText), + isDisabled: !toolbarProps.isNextEnabled, + }, + prevYear: { + text: options.prevYearText, + hint: formatWithOrdinals(options.prevHint, [options.yearText, 'year'], options.prevYearText), + isDisabled: false, + }, + nextYear: { + text: options.prevYearText, + hint: formatWithOrdinals(options.nextHint, [options.yearText, 'year'], options.nextYearText), + isDisabled: false, + }, + }; + for (const viewSpecName in viewSpecs) { + const viewSpec = viewSpecs[viewSpecName]; + const { singleUnit } = viewSpec; + const buttonTextKey = viewSpec.optionDefaults.buttonTextKey; + const buttonText = buttonConfigs[viewSpecName]?.text || + (buttonTextKey ? options[buttonTextKey] : '') || + (singleUnit ? getSingleUnitText(singleUnit, options) : '') || + viewSpecName; + const buttonHint = formatWithOrdinals(options.viewHint, [buttonText, viewSpecName], // ordinal arguments + buttonText); + buttonState[viewSpecName] = { + text: buttonText, + hint: buttonHint, + }; + } + return buttonState; + } + } + function getSingleUnitText(singleUnit, options) { + return options[singleUnit + 'TextLong'] || options[singleUnit + 'Text']; + } + + class CalendarMediaRoot extends C { + constructor() { + super(...arguments); + this.state = { + forPrint: false, + }; + this.handleBeforePrint = () => { + bn(() => { + this.setState({ forPrint: true }); + }); + }; + this.handleAfterPrint = () => { + bn(() => { + this.setState({ forPrint: false }); + }); + }; + } + render() { + return this.props?.children(this.state.forPrint); + } + componentDidMount() { + const { props } = this; + const { emitter } = props; + emitter.on('_beforeprint', this.handleBeforePrint); + emitter.on('_afterprint', this.handleAfterPrint); + } + componentWillUnmount() { + const { props } = this; + const { emitter } = props; + emitter.off('_beforeprint', this.handleBeforePrint); + emitter.off('_afterprint', this.handleAfterPrint); + } + } + function computeRootClassName(options, forPrint) { + let borderlessX = options.borderlessX ?? options.borderless; + let borderlessTop = options.borderlessTop ?? options.borderless; + let borderlessBottom = options.borderlessBottom ?? options.borderless; + const calendarDisplayData = { + borderlessX: Boolean(borderlessX), + borderlessTop: Boolean(borderlessTop), + borderlessBottom: Boolean(borderlessBottom), + }; + return joinClassNames(generateClassName(options.class, calendarDisplayData), generateClassName(options.className, calendarDisplayData), classNames.borderBoxRoot, classNames.isolate, classNames.flexCol, forPrint ? classNames.calendarPrintRoot : classNames.calendarScreenRoot); + } + + class ButtonIcon extends BaseComponent { + render() { + const { contentGenerator, className } = this.props; + if (contentGenerator) { + // TODO: somehow give className to the svg? + return (u$1(ContentContainer, { tag: 'span', style: { display: 'contents' }, attrs: { 'aria-hidden': true }, renderProps: {}, generatorName: undefined, customGenerator: contentGenerator })); + } + if (className !== undefined) { + return (u$1("span", { "aria-hidden": true, className: className })); + } + } + } + + class ToolbarSection extends BaseComponent { + render() { + let { props } = this; + let { options } = this.context; + let children = props.widgetGroups.map((widgetGroup) => this.renderWidgetGroup(widgetGroup)); + return k$1('div', { + className: generateClassName(options.toolbarSectionClass, { name: props.name }), + }, ...children); + } + renderWidgetGroup(widgetGroup) { + let { props, context } = this; + let { options } = context; + let children = []; + let isOnlyButtons = true; + let isOnlyView = true; + for (const widget of widgetGroup) { + const { name, isView } = widget; + if (name === 'title') { + isOnlyButtons = false; + } + else if (!isView) { + isOnlyView = false; + } + } + for (let widget of widgetGroup) { + let { name, customElement, buttonHint } = widget; + if (name === 'title') { + children.push(u$1("div", { role: 'heading', "aria-level": options.headingLevel, id: props.titleId, className: joinClassNames(options.toolbarTitleClass), children: props.title })); + } + else if (customElement) { + children.push(u$1(ContentContainer, { tag: 'span', style: { display: 'contents' }, renderProps: {}, generatorName: undefined, customGenerator: customElement })); + } + else { + let isSelected = name === props.selectedButton; + let isDisabled = (!props.isTodayEnabled && name === 'today') || + (!props.isPrevEnabled && name === 'prev') || + (!props.isNextEnabled && name === 'next'); + let buttonDisplay = widget.buttonDisplay ?? options.buttonDisplay; + if (buttonDisplay === 'auto') { + buttonDisplay = (widget.buttonIconContent || widget.buttonIconClass) + ? 'icon' + : 'text'; + } + let iconNode; + if (buttonDisplay !== 'text') { + iconNode = (u$1(ButtonIcon, { className: widget.buttonIconClass, contentGenerator: widget.buttonIconContent })); + } + let inGroup = widgetGroup.length > 1 && isOnlyButtons; + let buttonGroup = inGroup ? { hasSelection: isOnlyView } : null; + let renderProps = { + name, + text: widget.buttonText, + isPrimary: widget.buttonIsPrimary, + isSelected, + isDisabled, + isIconOnly: buttonDisplay === 'icon', + buttonGroup, + }; + children.push(u$1(ContentContainer, { tag: 'button', attrs: { + type: 'button', + disabled: isDisabled, + ...((isOnlyButtons && isOnlyView) + ? { 'role': 'tab', 'aria-selected': isSelected } + : { 'aria-pressed': isSelected }), + 'aria-label': typeof buttonHint === 'function' + ? buttonHint(props.navUnit) + : buttonHint, + onClick: widget.buttonClick, + }, className: joinClassNames(generateClassName(options.buttonClass, renderProps), !isDisabled && classNames.cursorPointer, inGroup && joinClassNames(isSelected ? classNames.z1 : classNames.z0, classNames.focusZ2)), renderProps: renderProps, generatorName: undefined, classNameGenerator: widget.buttonClass, didMount: widget.buttonDidMount, willUnmount: widget.buttonWillUnmount, children: () => (buttonDisplay === 'text' + ? widget.buttonText + : buttonDisplay === 'icon' + ? iconNode + : buttonDisplay === 'icon-text' + ? (u$1(S, { children: [iconNode, widget.buttonText] })) + : (u$1(S, { children: [widget.buttonText, iconNode] })) // text-icon + ) })); + } + } + if (children.length > 1) { + return k$1('div', { + role: (isOnlyButtons && isOnlyView) ? 'tablist' : undefined, + 'aria-label': (isOnlyButtons && isOnlyView) ? options.viewChangeHint : undefined, + className: joinClassNames(generateClassName(options.buttonGroupClass, { hasSelection: isOnlyView }), classNames.isolate), + }, ...children); + } + return children[0]; + } + } + + class Toolbar extends BaseComponent { + render() { + let { props } = this; + let options = this.context.options; + let { sectionWidgets } = props.model; + const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options); + const toolbarClassOption = props.isHeader ? options.headerToolbarClass : options.footerToolbarClass; + return (u$1("div", { className: joinClassNames(generateClassName(toolbarClassOption, { borderlessX, borderlessTop, borderlessBottom }), generateClassName(options.toolbarClass, { borderlessX, borderlessTop, borderlessBottom })), children: [this.renderSection('start', sectionWidgets.start), this.renderSection('center', sectionWidgets.center), this.renderSection('end', sectionWidgets.end)] })); + } + renderSection(name, widgetGroups) { + let { props } = this; + return (u$1(ToolbarSection, { name: name, widgetGroups: widgetGroups, title: props.title, titleId: props.titleId, navUnit: props.navUnit, selectedButton: props.selectedButton, isTodayEnabled: props.isTodayEnabled, isPrevEnabled: props.isPrevEnabled, isNextEnabled: props.isNextEnabled }, name)); + } + } + + /* + Detects when the user clicks on an event within a DateComponent + */ + class EventClicking extends Interaction { + constructor(settings) { + super(settings); + this.handleSegClick = (ev, segEl) => { + let { component } = this; + let { context } = component; + let eventRange = getElEventRange(segEl); + if (eventRange && // might be the
surrounding the more link + component.isValidSegDownEl(ev.target)) { + context.emitter.trigger('eventClick', { + el: segEl, + event: new EventImpl(component.context, eventRange.def, eventRange.instance), + jsEvent: ev, // Is this always a mouse event? See #4655 + view: context.viewApi, + }); + } + }; + this.destroy = listenBySelector(settings.el, 'click', `.${classNames.internalEvent}`, // on both fg and bg events + this.handleSegClick); + } + } + + /* + Triggers events and adds/removes core classNames when the user's pointer + enters/leaves event-elements of a component. + */ + class EventHovering extends Interaction { + constructor(settings) { + super(settings); + // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it + this.handleEventElRemove = (el) => { + if (el === this.currentSegEl) { + this.handleSegLeave(null, this.currentSegEl); + } + }; + this.handleSegEnter = (ev, segEl) => { + if (getElEventRange(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper + this.currentSegEl = segEl; + this.triggerEvent('eventMouseEnter', ev, segEl); + } + }; + this.handleSegLeave = (ev, segEl) => { + if (this.currentSegEl) { + this.currentSegEl = null; + this.triggerEvent('eventMouseLeave', ev, segEl); + } + }; + this.removeHoverListeners = listenToHoverBySelector(settings.el, `.${classNames.internalEvent}`, // on both fg and bg events + this.handleSegEnter, this.handleSegLeave); + } + destroy() { + this.removeHoverListeners(); + } + triggerEvent(publicEvName, ev, segEl) { + let { component } = this; + let { context } = component; + let eventRange = getElEventRange(segEl); + if (!ev || component.isValidSegDownEl(ev.target)) { + context.emitter.trigger(publicEvName, { + el: segEl, + event: new EventImpl(context, eventRange.def, eventRange.instance), + jsEvent: ev, // Is this always a mouse event? See #4655 + view: context.viewApi, + }); + } + } + } + + class CalendarInner extends PureComponent { + constructor() { + super(...arguments); + this.buildViewContext = memoize(buildViewContext); + this.buildViewPropTransformers = memoize(buildViewPropTransformers); + this.interactionsStore = {}; + this.calendarInteractions = []; + this.registerInteractiveComponent = (component, settingsInput) => { + let settings = parseInteractionSettings(component, settingsInput); + let DEFAULT_INTERACTIONS = [ + EventClicking, + EventHovering, + ]; + let interactionClasses = DEFAULT_INTERACTIONS; + if (!settingsInput.disableHits) { + interactionClasses = interactionClasses.concat(this.props.pluginHooks.componentInteractions); + } + let interactions = interactionClasses.map((TheInteractionClass) => new TheInteractionClass(settings)); + this.interactionsStore[component.uid] = interactions; + interactionSettingsStore[component.uid] = settings; + }; + this.unregisterInteractiveComponent = (component) => { + let listeners = this.interactionsStore[component.uid]; + if (listeners) { + for (let listener of listeners) { + listener.destroy(); + } + delete this.interactionsStore[component.uid]; + } + delete interactionSettingsStore[component.uid]; + }; + } + get viewTitleId() { + return this.props.baseId + 'title'; + } + render() { + const { props } = this; + let { toolbarConfig, options } = props; + let viewHeight; + let viewHeightLiquid = false; + let viewAspectRatio; + if (props.forPrint || getIsHeightAuto(options)) ; + else if (options.height != null) { + viewHeightLiquid = true; + } + else if (options.contentHeight != null) { + viewHeight = options.contentHeight; + } + else { + viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall + } + let viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.nowManager, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, props.baseId, this.registerInteractiveComponent, this.unregisterInteractiveComponent); + return (u$1(ViewContextType.Provider, { value: viewContext, children: [toolbarConfig.header && (u$1(Toolbar, { model: toolbarConfig.header, isHeader: true, titleId: this.viewTitleId, ...props.toolbarProps })), u$1("div", { className: joinClassNames(classNames.flexCol, classNames.rel, + // prevents browsers' "scroll anchoring behavior", which cause scroll thrashing + // when clicking "Next" for month-view, because rows would flex-grow while other rows + // temporarily removed. This behavior probably universally unhelpful for our uses, + // esp with virtualization, but maybe in future put on more specific row-based parents + classNames.overflowAnchorNone, + // workaround for Safari pushing content area extremely wide after returning from + // print-view. probably a good idea regardless, to circumvent 'auto' dimentions + classNames.minHeight0, viewHeightLiquid && classNames.liquid), style: { + height: viewHeight, + aspectRatio: viewAspectRatio != null ? String(viewAspectRatio) : undefined, + }, children: [this.renderView(joinClassNames((viewHeightLiquid || viewHeight) && classNames.liquid, viewAspectRatio != null && classNames.fill, classNames.internalView)), this.buildAppendContent()] }), toolbarConfig.footer && (u$1(Toolbar, { model: toolbarConfig.footer, isHeader: false, ...props.toolbarProps }))] })); + } + renderView(className) { + const { props } = this; + const { pluginHooks, viewSpec, toolbarConfig, toolbarProps } = props; + let viewProps = { + className, + dateProfile: props.dateProfile, + businessHours: props.businessHours, + eventStore: props.renderableEventStore, // ! + eventUiBases: props.eventUiBases, + dateSelection: props.dateSelection, + eventSelection: props.eventSelection, + eventDrag: props.eventDrag, + eventResize: props.eventResize, + forPrint: props.forPrint, + labelId: toolbarConfig.header && toolbarConfig.header.hasTitle ? this.viewTitleId : undefined, + labelStr: toolbarConfig.header && toolbarConfig.header.hasTitle ? undefined : toolbarProps.title, + }; + let transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers); + let contentProps = { + ...props, + toolbarProps, + forPrint: props.forPrint, + }; + for (let transformer of transformers) { + Object.assign(viewProps, transformer.transform(viewProps, contentProps)); + } + let ViewComponent = viewSpec.component; + return (u$1(ViewComponent, { ...viewProps })); + } + buildAppendContent() { + const { props } = this; + return (u$1(S, { children: props.pluginHooks.viewContainerAppends.map((buildAppendContent, i) => (u$1(S, { children: buildAppendContent(props) }, i))) })); + } + // BE AWARE React StrictMode might execute this twice + componentDidMount() { + const { props } = this; + this.calendarInteractions = props.pluginHooks.calendarInteractions + .map((CalendarInteractionClass) => new CalendarInteractionClass(props)); + let { propSetHandlers } = props.pluginHooks; + for (let propName in propSetHandlers) { + propSetHandlers[propName](props[propName], props); + } + // call contextInit + for (let callback of props.pluginHooks.contextInit) { + callback(props); + } + } + componentDidUpdate(prevProps) { + const { props } = this; + let { propSetHandlers } = props.pluginHooks; + for (let propName in propSetHandlers) { + if (props[propName] !== prevProps[propName]) { + propSetHandlers[propName](props[propName], props); + } + } + } + // BE AWARE React StrictMode might execute this twice + componentWillUnmount() { + const { props } = this; + for (let interaction of this.calendarInteractions) { + interaction.destroy(); + } + this.calendarInteractions = []; + // will likely undo what was done by contextInit + props.emitter.trigger('_unmount'); + } + } + function buildViewPropTransformers(theClasses) { + return theClasses.map((TheClass) => new TheClass()); + } + + function pointInsideRect(point, rect) { + return point.left >= rect.left && + point.left < rect.right && + point.top >= rect.top && + point.top < rect.bottom; + } + // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false + function intersectRects(rect1, rect2) { + let res = { + left: Math.max(rect1.left, rect2.left), + right: Math.min(rect1.right, rect2.right), + top: Math.max(rect1.top, rect2.top), + bottom: Math.min(rect1.bottom, rect2.bottom), + }; + if (res.left < res.right && res.top < res.bottom) { + return res; + } + return false; + } + // Returns a new point that will have been moved to reside within the given rectangle + function constrainPoint(point, rect) { + return { + left: Math.min(Math.max(point.left, rect.left), rect.right), + top: Math.min(Math.max(point.top, rect.top), rect.bottom), + }; + } + // Returns a point that is the center of the given rectangle + function getRectCenter(rect) { + return { + left: (rect.left + rect.right) / 2, + top: (rect.top + rect.bottom) / 2, + }; + } + // Subtracts point2's coordinates from point1's coordinates, returning a delta + function diffPoints(point1, point2) { + return { + left: point1.left - point2.left, + top: point1.top - point2.top, + }; + } + + function computeEdges(el, getPadding = false) { + let computedStyle = window.getComputedStyle(el); + let borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0; + let borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0; + let borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0; + let borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; + let badScrollbarWidths = computeScrollbarWidthsForEl(el); // includes border! + let scrollbarLeftRight = badScrollbarWidths.y - borderLeft - borderRight; + let scrollbarBottom = badScrollbarWidths.x - borderTop - borderBottom; + let res = { + borderLeft, + borderRight, + borderTop, + borderBottom, + scrollbarBottom, + scrollbarLeft: 0, + scrollbarRight: 0, + }; + if (computedStyle.direction === 'rtl') { + res.scrollbarLeft = scrollbarLeftRight; + } + else { + res.scrollbarRight = scrollbarLeftRight; + } + if (getPadding) { + res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0; + res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0; + res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0; + res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0; + } + return res; + } + function computeInnerRect(el, goWithinPadding = false, doFromWindowViewport) { + let outerRect = doFromWindowViewport ? el.getBoundingClientRect() : computeRect(el); + let edges = computeEdges(el, goWithinPadding); + let res = { + left: outerRect.left + edges.borderLeft + edges.scrollbarLeft, + right: outerRect.right - edges.borderRight - edges.scrollbarRight, + top: outerRect.top + edges.borderTop, + bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom, + }; + if (goWithinPadding) { + res.left += edges.paddingLeft; + res.right -= edges.paddingRight; + res.top += edges.paddingTop; + res.bottom -= edges.paddingBottom; + } + return res; + } + function computeRect(el) { + let rect = el.getBoundingClientRect(); + return { + left: rect.left + window.scrollX, + top: rect.top + window.scrollY, + right: rect.right + window.scrollX, + bottom: rect.bottom + window.scrollY, + }; + } + /* + Returns relative to viewport origin + */ + function computeClippedClientRect(el) { + let clippingParents = getClippingParents(el); + let rect = el.getBoundingClientRect(); + for (let clippingParent of clippingParents) { + let intersection = intersectRects(rect, clippingParent.getBoundingClientRect()); + if (intersection) { + rect = intersection; + } + else { + return null; + } + } + return rect; + } + // does not return window + function getClippingParents(el) { + let parents = []; + while (el instanceof HTMLElement) { // will stop when gets to document or null + let computedStyle = window.getComputedStyle(el); + if (computedStyle.position === 'fixed') { + break; + } + if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) { + parents.push(el); + } + el = el.parentNode; + } + return parents; + } + // WARNING: will include border + function computeScrollbarWidthsForEl(el) { + return { + x: el.offsetHeight - el.clientHeight, + y: el.offsetWidth - el.clientWidth, + }; + } + + function getAppendableRoot(el) { + const root = el.getRootNode(); + if (root instanceof Document) { + return root.body || root.documentElement; // pick body if available + } + return root; + } + function computeElIsRtl(el) { + return getComputedStyle(el).direction === 'rtl'; + } + // Style + // ---------------------------------------------------------------------------------------------------------------- + const PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i; + function applyStyle(el, props) { + for (let propName in props) { + applyStyleProp(el, propName, props[propName]); + } + } + function applyStyleProp(el, name, val) { + if (val == null) { + el.style[name] = ''; + } + else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) { + el.style[name] = `${val}px`; + } + else { + el.style[name] = val; + } + } + // Event Handling + // ---------------------------------------------------------------------------------------------------------------- + // if intercepting bubbled events at the document/window/body level, + // and want to see originating element (the 'target'), use this util instead + // of `ev.target` because it goes within web-component boundaries. + function getEventTargetViaRoot(ev) { + return ev.composedPath?.()[0] ?? ev.target; + } + + class NowTimer extends C { + constructor(props, context) { + super(props, context); + this.handleChange = () => { + this.forceUpdate(); + }; + this.runner = new NowTimerRunner(this.handleChange); + } + render() { + const { props, context } = this; + const { nowDate, todayRange } = this.runner.update({ + nowManager: context.nowManager, + unit: props.unit, + unitValue: props.unitValue, + nowIndicatorSnap: context.options.nowIndicatorSnap, + dateEnv: context.dateEnv, + }); + return props.children(nowDate, todayRange); + } + componentWillUnmount() { + this.runner.destroy(); + } + } + NowTimer.contextType = ViewContextType; + + const FULL_DATE_FORMAT = createFormatter({ year: 'numeric', month: 'long', day: 'numeric' }); + const WEEK_FORMAT = createFormatter({ week: 'long' }); + const WEEKDAY_ONLY_FORMAT = createFormatter({ + weekday: 'long', + }); + function findWeekdayText(parts) { + for (const part of parts) { + if (part.type === 'weekday') { + return part.value; + } + } + return ''; + } + function findDayNumberText(parts) { + for (const part of parts) { + if (part.type === 'day') { + return part.value; + } + } + return ''; + } + function findMonthText(parts) { + for (const part of parts) { + if (part.type === 'month') { + return part.value; + } + } + return ''; + } + + /* + TODO: just have this return the string? + */ + function buildDateStr(context, dateMarker, viewType = 'day') { + return joinDateTimeFormatParts(context.dateEnv.formatToParts(dateMarker, viewType === 'week' ? WEEK_FORMAT : FULL_DATE_FORMAT)); + } + /* + Assumes navLinks enabled + Always hidden to screen readers. Do not point aria-labelledby at this. Use aria-label instead. + */ + function buildNavLinkAttrs(context, dateMarker, viewType = 'day', dateStr = buildDateStr(context, dateMarker, viewType), isTabbable = true) { + const { dateEnv, options, calendarApi } = context; + const zonedDate = dateEnv.toDate(dateMarker); + const handleInteraction = (ev) => { + let customAction = viewType === 'day' ? options.navLinkDayClick : + viewType === 'week' ? options.navLinkWeekClick : null; + if (typeof customAction === 'function') { + customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev); + } + else { + if (typeof customAction === 'string') { + viewType = customAction; + } + calendarApi.zoomTo(dateMarker, viewType); + } + }; + return { + 'role': 'link', // TODO + 'aria-label': formatWithOrdinals(options.navLinkHint, [dateStr, zonedDate], dateStr), + 'className': joinClassNames(options.navLinkClass, classNames.cursorPointer, classNames.internalNavLink), + ...(isTabbable + ? createAriaClickAttrs(handleInteraction) + : { onClick: handleInteraction }), + }; + } + + function getDateMeta(dateMarker, dateEnv, dateProfile, todayRange, nowDate) { + const isDisabled = Boolean(dateProfile && (!dateProfile.activeRange || !rangeContainsMarker(dateProfile.activeRange, dateMarker))); + return { + date: dateEnv.toDate(dateMarker), + dow: dateMarker.getUTCDay(), + isDisabled, + isOther: !isDisabled && Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, dateMarker)), + isToday: !isDisabled && Boolean(todayRange && rangeContainsMarker(todayRange, dateMarker)), + isPast: !isDisabled && Boolean(nowDate ? (dateMarker < nowDate) : todayRange ? (dateMarker < todayRange.start) : false), + isFuture: !isDisabled && Boolean(nowDate ? (dateMarker > nowDate) : todayRange ? (dateMarker >= todayRange.end) : false), + }; + } + + function isDimsEqual(v0, v1) { + return v0 != null && (v0 === v1 || Math.abs(v0 - v1) < 0.01); + } + + const nativeBorderBoxEnabled = true; + const configMap = new Map(); + const afterSizeCallbacks = new Set(); + let isHandling = false; + let isStalling = false; + function afterSize(callback) { + afterSizeCallbacks.add(callback); + // batch & then flush when not within ResizeObserver handler loop + // happens for watchers that die and report `null` as dimension + if (!isHandling && !isStalling) { + isStalling = true; + requestAnimationFrame(() => { + isStalling = false; + flushAfterSize(); + }); + } + } + function flushAfterSize() { + for (const flushedCallback of afterSizeCallbacks.values()) { + flushedCallback(); + afterSizeCallbacks.delete(flushedCallback); + } + } + // Native + // ------------------------------------------------------------------------------------------------- + // Single global ResizeObserver does batching and uses less memory than individuals + // Will always fire with delay after DOM mutation, but before repaint, + // thus doesn't need !isHandling check like checkConfigMap + const globalResizeObserver = typeof ResizeObserver !== 'undefined' && new ResizeObserver((entries) => { + isHandling = true; + // // debug + // console.log('RESIZE-OBSERVER', entries.map((entry) => entry.target)) + for (let entry of entries) { + const el = entry.target; + const config = configMap.get(el); + let width; + let height; + if (entry.borderBoxSize && nativeBorderBoxEnabled) { + const borderBoxSize = entry.borderBoxSize[0] || entry.borderBoxSize; // HACK for Firefox + width = borderBoxSize.inlineSize; + height = borderBoxSize.blockSize; + } + else { + ({ width, height } = el.getBoundingClientRect()); + } + let shouldFire = false; + if (!isDimsEqual(config.width, width)) { + config.width = width; + shouldFire = config.watchWidth; + } + if (!isDimsEqual(config.height, height)) { + config.height = height; + shouldFire || (shouldFire = config.watchHeight); + } + if (shouldFire) { + config.callback(width, height); + } + } + bn(() => { + flushAfterSize(); + isHandling = false; + }); + }); + /* + PRECONDITION: element can only have one listener attached + */ + function watchSize(el, callback, watchWidth = true, watchHeight = true) { + configMap.set(el, { callback, watchWidth, watchHeight }); + // if statement is for jsdom and other shim environments that execute component effects, but + // haven't implemented ResizeObserver. Reference: https://github.com/jsdom/jsdom/issues/3368 + if (globalResizeObserver) { + globalResizeObserver.observe(el, { + box: 'border-box' + // default is 'content-box' + }); + } + return () => { + configMap.delete(el); + // same reasoning as above + if (globalResizeObserver) { + globalResizeObserver.unobserve(el); + } + }; + } + function watchWidth(el, callback) { + return watchSize(el, callback, + /* watchWidth = */ true); + } + function watchHeight(el, callback) { + return watchSize(el, (_width, height) => callback(height), + /* watchWidth = */ false, + /* watchHeight = */ true); + } + + class ViewContainer extends BaseComponent { + constructor() { + super(...arguments); + this.refineRenderProps = memoizeObjArg(refineRenderProps$1); + } + render() { + const { props, context } = this; + const { options, viewSpec } = context; + const renderProps = this.refineRenderProps({ + ...computeViewBorderless(options), + options: { headerToolbar: options.headerToolbar, footerToolbar: options.footerToolbar }, + isHeightAuto: getIsHeightAuto(options), + viewApi: context.viewApi, + }); + return (u$1(ContentContainer, { elRef: props.elRef, tag: props.tag || 'div', attrs: props.attrs, style: props.style, className: joinClassNames(props.className, generateClassName(options.viewClass, renderProps), + // WORKAROUND for way calendar's className would get merged into view's className + generateClassName(viewSpec.optionDefaults.class, renderProps), generateClassName(viewSpec.optionDefaults.className, renderProps), generateClassName(viewSpec.optionOverrides.class, renderProps), generateClassName(viewSpec.optionOverrides.className, renderProps)), renderProps: renderProps, generatorName: undefined, didMount: options.didMount || options.viewDidMount, willUnmount: options.willUnmount || options.viewWillUnmount, children: () => props.children })); + } + } + function refineRenderProps$1(raw) { + return { + view: raw.viewApi, + borderlessX: raw.borderlessX, + borderlessTop: raw.borderlessTop, + borderlessBottom: raw.borderlessBottom, + options: raw.options, + isHeightAuto: raw.isHeightAuto, + }; + } + + /* + an INTERACTABLE date component + + PURPOSES: + - hook up to fg, fill, and mirror renderers + - interface for dragging and hits + */ + class DateComponent extends BaseComponent { + constructor() { + super(...arguments); + this.uid = guid(); + } + // Hit System + // ----------------------------------------------------------------------------------------------------------------- + prepareHits() { + } + queryHit(isRtl, positionLeft, positionTop, elWidth, elHeight) { + return null; // this should be abstract + } + // Pointer Interaction Utils + // ----------------------------------------------------------------------------------------------------------------- + isValidSegDownEl(el) { + return !this.props.eventDrag && // HACK + !this.props.eventResize && // HACK + !el.closest(`.${classNames.internalEventMirror}`); + } + isValidDateDownEl(el) { + return !el.closest(`.${classNames.internalEvent}:not(.${classNames.internalBgEvent})`) && + !el.closest(`.${classNames.internalMoreLink}`) && + !el.closest(`.${classNames.internalNavLink}`) && + !el.closest(`.${classNames.internalPopover}`); // hack + } + } + + class DelayedRunner { + constructor(drainedOption) { + this.drainedOption = drainedOption; + this.isRunning = false; + this.isDirty = false; + this.pauseDepths = {}; + this.timeoutId = 0; + } + request(delay) { + this.isDirty = true; + if (!this.isPaused()) { + this.clearTimeout(); + if (delay == null) { + this.tryDrain(); + } + else { + this.timeoutId = setTimeout(// NOT OPTIMAL! TODO: look at debounce + this.tryDrain.bind(this), delay); + } + } + } + pause(scope = '') { + let { pauseDepths } = this; + pauseDepths[scope] = (pauseDepths[scope] || 0) + 1; + this.clearTimeout(); + } + resume(scope = '', force) { + let { pauseDepths } = this; + if (scope in pauseDepths) { + if (force) { + delete pauseDepths[scope]; + } + else { + pauseDepths[scope] -= 1; + let depth = pauseDepths[scope]; + if (depth <= 0) { + delete pauseDepths[scope]; + } + } + this.tryDrain(); + } + } + isPaused() { + return Object.keys(this.pauseDepths).length; + } + tryDrain() { + if (!this.isRunning && !this.isPaused()) { + this.isRunning = true; + while (this.isDirty) { + this.isDirty = false; + this.drained(); // might set isDirty to true again + } + this.isRunning = false; + } + } + clear() { + this.clearTimeout(); + this.isDirty = false; + this.pauseDepths = {}; + } + clearTimeout() { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + this.timeoutId = 0; + } + } + drained() { + if (this.drainedOption) { + this.drainedOption(); + } + } + } + + /* + NOTE: detection is complicated (w/ touch and wheel) because ScrollerSyncer needs to know about it, + but are we sure we can't just ignore programmatic scrollTo() calls with a flag? and determine the + the scroll-master simply by who was the newest scroller? Does passive:true do things asynchronously? + */ + class ScrollListener { + constructor(el) { + this.el = el; + this.emitter = new Emitter(); + this.isScroll = false; + this.isScrollRecent = false; + this.isWheelRecent = false; + this.isMouseDown = false; // user currently has mouse down? + this.isTouchDown = false; // user currently has finger down? + // accumulated during scroll + this.isMouse = false; + this.isTouch = false; + this.isWheel = false; + // Handlers + // ---------------------------------------------------------------------------------------------- + this.handleScroll = () => { + this.isScrollRecent = true; + if (this.isMouseDown) { + this.isMouse = true; + } + if (this.isTouchDown) { + this.isTouch = true; + } + if (this.isWheelRecent) { + this.isWheel = true; + } + this.startScroll(); + this.emitter.trigger('scroll', this.getIsDevice()); + this.scrollWaiter.request(500); + }; + this.handleScrollWait = () => { + this.isScrollRecent = false; + // only end the scroll if not currently touching. + // if touching, the scrolling will end later, on touchend. + if (!this.isTouchDown) { + this.endScroll(); + } + }; + // will fire *before* the scroll event is fired (might not cause a scroll!) + this.handleWheel = () => { + this.isWheelRecent = true; + this.wheelWaiter.request(500); + }; + this.handleWheelWait = () => { + this.isWheelRecent = false; + }; + this.handleMouseDown = () => { + this.isMouseDown = true; + }; + this.handleMouseUp = () => { + this.isMouseDown = false; + }; + // will fire *before* the scroll event is fired (might not cause a scroll!) + this.handleTouchStart = () => { + this.isTouchDown = true; + }; + this.handleTouchEnd = () => { + this.isTouchDown = false; + // if the user ended their touch, and the scroll area wasn't moving, + // we consider this to be the end of the scroll + // otherwise, wait for inertia to finish and handleScrollWait to fire + if (!this.isScrollRecent) { + this.endScroll(); + } + }; + this.wheelWaiter = new DelayedRunner(this.handleWheelWait); + this.scrollWaiter = new DelayedRunner(this.handleScrollWait); + el.addEventListener('scroll', this.handleScroll, { passive: true }); + el.addEventListener('wheel', this.handleWheel, { passive: true }); + el.addEventListener('mousedown', this.handleMouseDown); + el.addEventListener('mouseup', this.handleMouseUp); + el.addEventListener('touchstart', this.handleTouchStart, { passive: true }); + el.addEventListener('touchend', this.handleTouchEnd); + } + destroy() { + let { el } = this; + el.removeEventListener('scroll', this.handleScroll, { passive: true }); + el.removeEventListener('wheel', this.handleWheel, { passive: true }); + el.removeEventListener('mousedown', this.handleMouseDown); + el.removeEventListener('mouseup', this.handleMouseUp); + el.removeEventListener('touchstart', this.handleTouchStart, { passive: true }); + el.removeEventListener('touchend', this.handleTouchEnd); + } + // Start / Stop + // ---------------------------------------------------------------------------------------------- + startScroll() { + if (!this.isScroll) { + this.isScroll = true; + this.emitter.trigger('scrollStart', this.getIsDevice()); + } + } + endScroll() { + if (this.isScroll) { // extra protection because might be called publicly + this.scrollWaiter.clear(); // (same) + this.wheelWaiter.clear(); // (same) + this.isScroll = false; + this.isWheelRecent = false; + this.emitter.trigger('scrollEnd', this.getIsDevice()); + this.isMouse = false; + this.isTouch = false; + this.isWheel = false; + } + } + getIsDevice() { + return this.isWheel || this.isMouse || this.isTouch; + } + } + + class Scroller extends DateComponent { + constructor() { + super(...arguments); + this.handleEl = (el) => { + if (this.el) { + this.el = null; + this._isUnmounting = true; + this.listener.destroy(); + } + if (el) { + this.el = el; + this._isUnmounting = false; + this.listener = new ScrollListener(el); + } + }; + this.handleHRuler = (el) => { + if (this.disconnectHRuler) { + this.disconnectHRuler(); + this.disconnectHRuler = undefined; + if (this.clientWidth !== undefined) { + this.clientWidth = undefined; + setRef(this.props.clientWidthRef, null); + } + } + if (el) { + this.disconnectHRuler = watchWidth(el, (clientWidth) => { + if (this._isUnmounting) + return; + if (clientWidth !== this.clientWidth) { + this.clientWidth = clientWidth; + setRef(this.props.clientWidthRef, clientWidth); + } + }); + } + }; + this.handleVRuler = (el) => { + if (this.disconnectVRuler) { + this.disconnectVRuler(); + this.disconnectVRuler = undefined; + if (this.clientHeight !== undefined) { + this.clientHeight = undefined; + setRef(this.props.clientHeightRef, null); + } + } + if (el) { + this.disconnectVRuler = watchHeight(el, (clientHeight) => { + if (this._isUnmounting) + return; + if (clientHeight !== this.clientHeight) { + this.clientHeight = clientHeight; + setRef(this.props.clientHeightRef, clientHeight); + } + const bottomScrollbarWidth = Math.round(this.el.getBoundingClientRect().height - clientHeight); + if (bottomScrollbarWidth !== this.bottomScrollbarWidth) { + this.bottomScrollbarWidth = bottomScrollbarWidth; + setRef(this.props.bottomScrollbarWidthRef, bottomScrollbarWidth); + } + }); + } + }; + } + render() { + const { props } = this; + // if there's only one axis that needs scrolling, the other axis will unintentionally have + // scrollbars too if we don't force to 'hidden' + const fallbackOverflow = (props.horizontal || props.vertical) ? 'hidden' : ''; + return (u$1("div", { ref: this.handleEl, className: joinClassNames(props.className, classNames.noPadding, classNames.rel, // for children fillTop/fillStart + props.hideScrollbars && classNames.noScrollbars, classNames.internalScroller), style: { + ...props.style, + overflowX: (props.horizontal ? 'auto' : fallbackOverflow), + overflowY: (props.vertical ? 'auto' : fallbackOverflow), + }, children: [props.children, Boolean(props.clientWidthRef) && (u$1("div", { ref: this.handleHRuler, className: classNames.fillTop })), Boolean(props.clientHeightRef || props.bottomScrollbarWidthRef) && (u$1("div", { ref: this.handleVRuler, className: classNames.fillStart }))] })); + } + endScroll() { + this.listener.endScroll(); + } + // Public API + // ----------------------------------------------------------------------------------------------- + get x() { + const { el } = this; + return el ? getNormalizedScrollX(el) : 0; + } + get y() { + const { el } = this; + return el ? el.scrollTop : 0; + } + scrollTo({ x, y }) { + const { el } = this; + if (el) { + if (y != null) { + el.scrollTop = y; + } + if (x != null) { + setNormalizedScrollX(el, x); + } + } + } + addScrollStartListener(handler) { + this.listener.emitter.on('scrollStart', handler); + } + removeScrollStartListener(handler) { + this.listener.emitter.off('scrollStart', handler); + } + addScrollEndListener(handler) { + this.listener.emitter.on('scrollEnd', handler); + } + removeScrollEndListener(handler) { + this.listener.emitter.off('scrollEnd', handler); + } + } + // Public API + // ------------------------------------------------------------------------------------------------- + // We can drop normalization when support for Chromium-based <86 is dropped (see Notion) + function getNormalizedScrollX(el) { + const { scrollLeft } = el; + const isRtl = computeElIsRtl(el); + return isRtl ? getNormalizedRtlScrollX(scrollLeft, el) : scrollLeft; + } + function setNormalizedScrollX(el, x) { + const isRtl = computeElIsRtl(el); + el.scrollLeft = isRtl ? getNormalizedRtlScrollLeft(x, el) : x; + } + /* + Returns a value in the 'reverse' system + */ + function getNormalizedRtlScrollX(scrollLeft, el) { + switch (getRtlScrollerSystem()) { + case 'positive': + return el.scrollWidth - el.clientWidth - scrollLeft; + case 'negative': + return -scrollLeft; + } + return scrollLeft; + } + /* + Receives a value in the 'reverse' system + TODO: is this really the same equations as getNormalizedRtlScrollX??? I think so + If so, consolidate. With isRtl check too + */ + function getNormalizedRtlScrollLeft(x, el) { + switch (getRtlScrollerSystem()) { + case 'positive': + return el.scrollWidth - el.clientWidth - x; + case 'negative': + return -x; + } + return x; + } + let _rtlScrollerSystem; + function getRtlScrollerSystem() { + return _rtlScrollerSystem || (_rtlScrollerSystem = detectRtlScrollerSystem()); + } + function detectRtlScrollerSystem() { + let el = document.createElement('div'); + el.style.position = 'absolute'; + el.style.top = '-1000px'; + el.style.width = '100px'; // must be at least the side of scrollbars or you get inaccurate values (#7335) + el.style.height = '100px'; // " + el.style.overflow = 'scroll'; + el.style.direction = 'rtl'; + let innerEl = document.createElement('div'); + innerEl.style.width = '200px'; + innerEl.style.height = '200px'; + el.appendChild(innerEl); + document.body.appendChild(el); + let system; + if (el.scrollLeft > 0) { + system = 'positive'; // scroll is a positive number from the left edge + } + else { + el.scrollLeft = 50; + if (el.scrollLeft > 0) { + system = 'reverse'; // scroll is a positive number from the right edge + } + else { + system = 'negative'; // scroll is a negative number from the right edge + } + } + el.remove(); + return system; + } + + class StandardEvent extends BaseComponent { + constructor() { + super(...arguments); + // memo + this.buildPublicEvent = memoize((context, eventDef, eventInstance) => new EventImpl(context, eventDef, eventInstance)); + this.handleEl = (el) => { + this.el = el; + setRef(this.props.elRef, el); + if (el) { + setElEventRange(el, this.props.eventRange); + } + }; + } + render() { + const { props, context } = this; + const { options } = context; + const { eventRange } = props; + const eventUi = eventRange.ui; + const timeFormat = options.eventTimeFormat || props.defaultTimeFormat; + const timeText = props.forcedTimeText ?? buildEventRangeTimeText(timeFormat, eventRange, // just for def/instance + props.slicedStart, props.slicedEnd, props.isStart, props.isEnd, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd); + const [tag, attrs, isInteractive] = getEventTagAndAttrs(eventRange, context); + const eventApi = this.buildPublicEvent(context, eventRange.def, eventRange.instance); + const isDraggable = !props.disableDragging && computeEventRangeDraggable(eventRange, context); + const isBlock = /row|column/.test(props.display); + const subcontentRenderProps = { + event: eventApi, + isNarrow: props.isNarrow || false, + isShort: props.isShort || false, + timeText, + }; + const renderProps = { + event: eventApi, // make stable. everything else atomic. FYI, eventRange unfortunately gets reconstructed a lot, but def/instance is stable + view: context.viewApi, + timeText: timeText, + color: eventUi.color || options.eventColor, + contrastColor: eventUi.contrastColor || options.eventContrastColor, + isDraggable, + isStartResizable: !props.disableResizing && props.isStart && eventUi.durationEditable && options.eventResizableFromStart, + isEndResizable: !props.disableResizing && props.isEnd && eventUi.durationEditable, + isMirror: props.isMirror, + isStart: Boolean(props.isStart), + isEnd: Boolean(props.isEnd), + isFirst: Boolean(props.isFirst), + isLast: Boolean(props.isLast), + isPast: Boolean(props.isPast), // TODO: don't cast. getDateMeta does it + isFuture: Boolean(props.isFuture), // TODO: don't cast. getDateMeta does it + isToday: Boolean(props.isToday), // TODO: don't cast. getDateMeta does it + isSelected: Boolean(props.isSelected), + isDragging: Boolean(props.isDragging), + isResizing: Boolean(props.isResizing), + isInteractive, + isNarrow: props.isNarrow || false, + isShort: props.isShort || false, + level: props.level || 0, + timeClass: joinClassNames(generateClassName(options.eventTimeClass, subcontentRenderProps), isBlock && generateClassName(options.blockEventTimeClass, subcontentRenderProps), props.display === 'row' && generateClassName(options.rowEventTimeClass, subcontentRenderProps), props.display === 'column' && generateClassName(options.columnEventTimeClass, subcontentRenderProps), props.display === 'list-item' && generateClassName(options.listItemEventTimeClass, subcontentRenderProps)), + titleClass: joinClassNames(generateClassName(options.eventTitleClass, subcontentRenderProps), isBlock && generateClassName(options.blockEventTitleClass, subcontentRenderProps), props.display === 'row' && generateClassName(options.rowEventTitleClass, subcontentRenderProps), props.display === 'column' && generateClassName(options.columnEventTitleClass, subcontentRenderProps), props.display === 'list-item' && generateClassName(options.listItemEventTitleClass, subcontentRenderProps), props.display === 'row' && options.rowEventTitleSticky && classNames.stickyS, props.display === 'column' && options.columnEventTitleSticky && classNames.stickyT), + options: { eventOverlap: Boolean(options.eventOverlap) }, + }; + const outerClassName = joinClassNames(// already includes eventClass below + isBlock && generateClassName(options.blockEventClass, renderProps), props.display === 'row' && generateClassName(options.rowEventClass, renderProps), props.display === 'column' && generateClassName(options.columnEventClass, renderProps), props.display === 'list-item' && generateClassName(options.listItemEventClass, renderProps), eventUi.className, props.className, props.display === 'column' + ? classNames.flexCol + : classNames.flexRow, (eventRange.def.url || isDraggable) && classNames.cursorPointer, classNames.internalEvent, props.isMirror && classNames.internalEventMirror, isDraggable && classNames.internalEventDraggable, renderProps.isSelected && classNames.internalEventSelected, (renderProps.isStartResizable || renderProps.isEndResizable) && classNames.internalEventResizable); + const beforeClassName = joinClassNames(generateClassName(options.eventBeforeClass, renderProps), isBlock && generateClassName(options.blockEventBeforeClass, renderProps), props.display === 'row' && generateClassName(options.rowEventBeforeClass, renderProps), props.display === 'column' && generateClassName(options.columnEventBeforeClass, renderProps), props.display === 'list-item' && generateClassName(options.listItemEventBeforeClass, renderProps)); + const afterClassName = joinClassNames(generateClassName(options.eventAfterClass, renderProps), isBlock && generateClassName(options.blockEventAfterClass, renderProps), props.display === 'row' && generateClassName(options.rowEventAfterClass, renderProps), props.display === 'column' && generateClassName(options.columnEventAfterClass, renderProps), props.display === 'list-item' && generateClassName(options.listItemEventAfterClass, renderProps)); + const innerClassName = joinClassNames(generateClassName(options.eventInnerClass, renderProps), isBlock && generateClassName(options.blockEventInnerClass, renderProps), props.display === 'row' && generateClassName(options.rowEventInnerClass, renderProps), props.display === 'column' && generateClassName(options.columnEventInnerClass, renderProps), props.display === 'list-item' && generateClassName(options.listItemEventInnerClass, renderProps), !props.disableLiquid && classNames.liquid); + const beforeContent = props.display === 'row' && options.rowEventBeforeContent; + const afterContent = props.display === 'row' && options.rowEventAfterContent; + return (u$1(ContentContainer, { tag: tag, attrs: { + ...props.attrs, + ...attrs, + // HACK because this event-element gets attached to root during some dragging + dir: (props.isDragging && options.direction === 'rtl') ? 'rtl' : undefined, + }, className: outerClassName, style: { + '--fc-event-color': renderProps.color, + '--fc-event-contrast-color': renderProps.contrastColor, + }, elRef: this.handleEl, renderProps: renderProps, generatorName: "eventContent", customGenerator: options.eventContent, defaultGenerator: renderInnerContent$2, classNameGenerator: options.eventClass, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount, children: (InnerContent) => (u$1(S, { children: [Boolean(renderProps.isSelected && isBlock) && (u$1("div", { className: props.display === 'column' + ? classNames.hitX + : classNames.hitY })), (beforeClassName || beforeContent) && (u$1("div", { className: joinClassNames(beforeClassName, !props.disableZindexes && classNames.z1, renderProps.isStartResizable && joinClassNames(props.display === 'column' + ? classNames.cursorResizeT + : classNames.cursorResizeS, + // these classnames required for dnd + classNames.internalEventResizer, classNames.internalEventResizerStart)), children: [beforeContent && (u$1(ContentContainer, { tag: 'div', style: { display: 'contents' }, attrs: { 'aria-hidden': true }, renderProps: renderProps, generatorName: undefined, customGenerator: beforeContent })), Boolean(renderProps.isStartResizable && renderProps.isSelected) && (u$1("div", { className: classNames.hit }))] })), u$1(InnerContent, { tag: "div", className: joinClassNames(innerClassName, !props.disableZindexes && classNames.z0) }), (afterClassName || afterContent) && (u$1("div", { className: joinClassNames(afterClassName, !props.disableZindexes && classNames.z1, renderProps.isEndResizable && joinClassNames(props.display === 'column' + ? classNames.cursorResizeB + : classNames.cursorResizeE, + // these classnames required for dnd + classNames.internalEventResizer, classNames.internalEventResizerEnd)), children: [afterContent && (u$1(ContentContainer, { tag: 'div', style: { display: 'contents' }, attrs: { 'aria-hidden': true }, renderProps: renderProps, generatorName: undefined, customGenerator: afterContent })), Boolean(renderProps.isEndResizable && renderProps.isSelected) && (u$1("div", { className: classNames.hit }))] }))] })) })); + } + componentDidUpdate(prevProps) { + if (this.el && this.props.eventRange !== prevProps.eventRange) { + setElEventRange(this.el, this.props.eventRange); + } + } + } + StandardEvent.addPropsEquality({ + seg: isPropsEqualShallow, + }); + function renderInnerContent$2(innerProps) { + return (u$1(S, { children: [innerProps.timeText && (u$1("div", { className: innerProps.timeClass, children: innerProps.timeText })), u$1("div", { className: innerProps.titleClass, children: innerProps.event.title || u$1(S, { children: "\u00A0" }) })] })); + } + + class Slicer { + constructor() { + this.sliceBusinessHours = memoize(this._sliceBusinessHours); + this.sliceDateSelection = memoize(this._sliceDateSpan); + this.sliceEventStore = memoize(this._sliceEventStore); + this.sliceEventDrag = memoize(this._sliceInteraction); + this.sliceEventResize = memoize(this._sliceInteraction); + this.forceDayIfListItem = false; // hack + } + sliceProps(props, dateProfile, nextDayThreshold, context, ...extraArgs) { + let { eventUiBases } = props; + let eventSegs = this.sliceEventStore(props.eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs); + return { + dateSelectionSegs: this.sliceDateSelection(props.dateSelection, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs), + businessHourSegs: this.sliceBusinessHours(props.businessHours, dateProfile, nextDayThreshold, context, ...extraArgs), + fgEventSegs: eventSegs.fg, + bgEventSegs: eventSegs.bg, + eventDrag: this.sliceEventDrag(props.eventDrag, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs), + eventResize: this.sliceEventResize(props.eventResize, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs), + eventSelection: props.eventSelection, + }; // TODO: give interactionSegs? + } + sliceNowDate(// does not memoize + date, dateProfile, nextDayThreshold, context, ...extraArgs) { + return this._sliceDateSpan({ range: { start: date, end: addMs(date, 1) }, allDay: false }, // add 1 ms, protect against null range + dateProfile, nextDayThreshold, {}, context, ...extraArgs); + } + _sliceBusinessHours(businessHours, dateProfile, nextDayThreshold, context, ...extraArgs) { + if (!businessHours) { + return []; + } + return this._sliceEventStore(expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), context), {}, dateProfile, nextDayThreshold, ...extraArgs).bg; + } + _sliceEventStore(eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) { + if (eventStore) { + let rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + bg: this.sliceEventRanges(rangeRes.bg, extraArgs), + fg: this.sliceEventRanges(rangeRes.fg, extraArgs), + }; + } + return { bg: [], fg: [] }; + } + _sliceInteraction(interaction, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) { + if (!interaction) { + return null; + } + let rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + segs: this.sliceEventRanges(rangeRes.fg, extraArgs), + affectedInstances: interaction.affectedEvents.instances, + isEvent: interaction.isEvent, + }; + } + _sliceDateSpan(dateSpan, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs) { + if (!dateSpan) { + return []; + } + let activeRange = computeActiveRange(dateProfile, Boolean(nextDayThreshold)); + let activeDateSpanRange = intersectRanges(dateSpan.range, activeRange); + if (activeDateSpanRange) { + dateSpan = { ...dateSpan, range: activeDateSpanRange }; + let eventRange = fabricateEventRange(dateSpan, eventUiBases, context); + let segs = this.sliceRange(dateSpan.range, ...extraArgs); + for (let seg of segs) { + seg.eventRange = eventRange; + } + return segs; + } + return []; + } + /* + "complete" seg means it has component and eventRange + */ + sliceEventRanges(eventRanges, extraArgs) { + let segs = []; + for (let eventRange of eventRanges) { + segs.push(...this.sliceEventRange(eventRange, extraArgs)); + } + return segs; + } + /* + "complete" seg means it has component and eventRange + */ + sliceEventRange(eventRange, extraArgs) { + let dateRange = eventRange.range; + // hack to make multi-day events that are being force-displayed as list-items to take up only one day + if (this.forceDayIfListItem && eventRange.ui.display === 'list-item') { + dateRange = { + start: dateRange.start, + end: addDays(dateRange.start, 1), + }; + } + let segs = this.sliceRange(dateRange, ...extraArgs); // !!! + for (let seg of segs) { + seg.eventRange = eventRange; + seg.isStart = eventRange.isStart && seg.isStart; + seg.isEnd = eventRange.isEnd && seg.isEnd; + } + return segs; + } + } + /* + for incorporating slotMinTime/slotMaxTime if appropriate + TODO: should be part of DateProfile! + TimelineDateProfile already does this btw + */ + function computeActiveRange(dateProfile, isComponentAllDay) { + let range = dateProfile.activeRange; + if (isComponentAllDay) { + return range; + } + return { + start: addMs(range.start, dateProfile.slotMinTime.milliseconds), + end: addMs(range.end, dateProfile.slotMaxTime.milliseconds - 864e5), // 864e5 = ms in a day + }; + } + + class DayTableSlicer extends Slicer { + constructor() { + super(...arguments); + this.forceDayIfListItem = true; + } + sliceRange(dateRange, dayTableModel) { + return dayTableModel.sliceRange(dateRange); + } + } + + // TODO: converge types with DayTableCell and DayCellContainer (the component) and refineRenderProps + // the generation of DayTableCell will be distinct (for the BODY cells) + // but can share some of the same types/utils + // Date Cells + // ------------------------------------------------------------------------------------------------- + const firstSunday = new Date(259200000); + function buildDateRowConfigs(dates, datesRepDistinctDays, dateProfile, todayRange, dayHeaderFormat, // TODO: rename to dateHeaderFormat? + context) { + const rowConfig = buildDateRowConfig(dates, datesRepDistinctDays, dateProfile, todayRange, dayHeaderFormat, context); + const majorUnit = computeMajorUnit(dateProfile, context.dateEnv); + // HACK mutate isMajor + // Skip 'day' majorUnit: when each header cell IS a day, every cell would match, + // so there's no meaningful boundary to highlight (unlike timeline slots which can be sub-day). + if (datesRepDistinctDays && majorUnit !== 'day') { + for (const dataConfig of rowConfig.dataConfigs) { + if (isMajorUnit(dataConfig.dateMarker, majorUnit, context.dateEnv)) { + dataConfig.renderProps.isMajor = true; + } + } + } + return [rowConfig]; + } + /* + Should this receive resource data attributes? + Or ResourceApi object itself? + */ + function buildDateRowConfig(dateMarkers, datesRepDistinctDays, dateProfile, todayRange, dayHeaderFormat, // TODO: rename to dateHeaderFormat? + context, colSpan, isMajorMod) { + return { + isDateRow: true, + renderConfig: buildDateRenderConfig(dayHeaderFormat, datesRepDistinctDays, context), + dataConfigs: buildDateDataConfigs(dateMarkers, datesRepDistinctDays, dateProfile, todayRange, dayHeaderFormat, context, colSpan, undefined, undefined, undefined, undefined, isMajorMod) + }; + } + /* + For header cells: how to connect w/ custom rendering + Applies to all cells in a row + */ + function buildDateRenderConfig(dayHeaderFormat, datesRepDistinctDays, context) { + const { options } = context; + return { + generatorName: 'dayHeaderContent', + customGenerator: options.dayHeaderContent, + classNameGenerator: options.dayHeaderClass, + innerClassNameGenerator: options.dayHeaderInnerClass, + didMount: options.dayHeaderDidMount, + willUnmount: options.dayHeaderWillUnmount, + align: options.dayHeaderAlign, + sticky: options._dayHeaderSticky, + dayHeaderFormat, + datesRepDistinctDays, + }; + } + const dowDates = []; + for (let dow = 0; dow < 7; dow++) { + dowDates.push(addDays(new Date(259200000), dow)); // start with Sun, 04 Jan 1970 00:00:00 GMT) + } + /* + For header cells: data + */ + function buildDateDataConfigs(dateMarkers, datesRepDistinctDays, dateProfile, todayRange, dayHeaderFormat, // TODO: rename to dateHeaderFormat? + context, colSpan = 1, keyPrefix = '', extraRenderProps = {}, // TODO + extraAttrs = {}, // TODO + className = '', isMajorMod) { + const { dateEnv, viewApi, options } = context; + return datesRepDistinctDays + ? dateMarkers.map((dateMarker, i) => { + const dateMeta = getDateMeta(dateMarker, dateEnv, dateProfile, todayRange); + const isMajor = isMajorMod != null && !(i % isMajorMod); + const hasNavLink = options.navLinks && !dateMeta.isDisabled && + dateMarkers.length > 1; // don't show navlink to day if only one day + const renderProps = { + ...dateMeta, + ...extraRenderProps, + isMajor, + isSticky: false, // HACK. gets overridden + inPopover: false, + hasNavLink, + view: viewApi, + }; + const fullDateStr = buildDateStr(context, dateMarker); + // for DayGridHeaderCell + return { + key: keyPrefix + dateMarker.toUTCString(), + dateMarker, + renderProps, + attrs: { + 'aria-label': fullDateStr, + ...(dateMeta.isToday ? { 'aria-current': 'date' } : {}), // TODO: assign undefined for nonexistent + 'data-date': formatDayString(dateMarker), + ...extraAttrs, + }, + // for navlink + innerAttrs: hasNavLink + ? buildNavLinkAttrs(context, dateMarker, undefined, fullDateStr) + : { 'aria-hidden': true }, // label already on cell + colSpan, + hasNavLink, + className, + }; + }) + : dateMarkers.map((dateMarker, i) => { + const dow = dateMarker.getUTCDay(); + const normDate = addDays(firstSunday, dow); + const dateMeta = { + date: dateEnv.toDate(dateMarker), + dow, + isDisabled: false, + isFuture: false, + isPast: false, + isToday: false, + isOther: false, + }; + const isMajor = isMajorMod != null && !(i % isMajorMod); + const renderProps = { + ...dateMeta, + date: dowDates[dow], + isMajor, + isSticky: false, // HACK. gets overridden + inPopover: false, + hasNavLink: false, + view: viewApi, + ...extraRenderProps, + }; + const fullWeekDayStr = joinDateTimeFormatParts(dateEnv.formatToParts(normDate, WEEKDAY_ONLY_FORMAT)); + // for DayGridHeaderCell + return { + key: keyPrefix + String(dow), + dateMarker, + renderProps, + attrs: { + 'aria-label': fullWeekDayStr, + ...extraAttrs, + }, + // NOT a navlink + innerAttrs: { + 'aria-hidden': true, // label already on cell + }, + colSpan, + className, + }; + }); + } + + /* + TODO: make API where createRefMap() called + */ + class RefMap { + constructor(masterCallback, ignoreDeletes = false) { + this.masterCallback = masterCallback; + this.ignoreDeletes = ignoreDeletes; + this.rev = ''; + this.current = new Map(); + this.callbacks = new Map; + this.handleValue = (val, key) => { + let { current, callbacks } = this; + if (val === null) { + if (!this.ignoreDeletes) { + current.delete(key); + callbacks.delete(key); + } + } + else { + current.set(key, val); + } + this.rev = guid(); + if (this.masterCallback) { + this.masterCallback(val, key); + } + }; + } + createRef(key) { + let refCallback = this.callbacks.get(key); + if (!refCallback) { + refCallback = (val) => { + this.handleValue(val, key); + }; + this.callbacks.set(key, refCallback); + } + return refCallback; + } + } + + class Ruler extends BaseComponent { + constructor() { + super(...arguments); + this.elRef = M$1(); + } + render() { + return (u$1("div", { ref: this.elRef })); + } + componentDidMount() { + this._isUnmounting = false; + const { props } = this; + const el = this.elRef.current; + this.disconnectWidth = watchWidth(el, (width) => { + if (this._isUnmounting) + return; + setRef(props.widthRef, width); + }); + } + componentWillUnmount() { + this._isUnmounting = true; + this.disconnectWidth(); + const { props } = this; + if (props.widthRef) { + setRef(props.widthRef, null); + } + } + } + + /* + We need really specific keys because RefMap::createRef() which is then given to heightRef + unable to change key! As a result, we cannot reuse elements between normal/slice/standin types, + but that's okay since they render quite differently + */ + function getEventPartKey(seg) { + return getEventKey(seg) + ':' + seg.start + + (seg.standinFor ? ':standin' : seg.isSlice ? ':slice' : ''); + } + // DayGridRange utils (TODO: move) + // ------------------------------------------------------------------------------------------------- + function splitSegsByRow(segs, rowCount) { + const byRow = []; + for (let row = 0; row < rowCount; row++) { + byRow[row] = []; + } + for (const seg of segs) { + byRow[seg.row].push(seg); + } + return byRow; + } + function splitInteractionByRow(ui, rowCount) { + const byRow = []; + if (!ui) { + for (let row = 0; row < rowCount; row++) { + byRow[row] = null; + } + } + else { + for (let row = 0; row < rowCount; row++) { + byRow[row] = { + affectedInstances: ui.affectedInstances, + isEvent: ui.isEvent, + segs: [], + }; + } + for (const seg of ui.segs) { + byRow[seg.row].segs.push(seg); + } + } + return byRow; + } + function sliceSegForCol(seg, col) { + return { + ...seg, + start: col, + end: col + 1, + isStart: seg.isStart && seg.start === col, + isEnd: seg.isEnd && seg.end - 1 === col, + standinFor: seg, + }; + } + + class BgEvent extends BaseComponent { + constructor() { + super(...arguments); + // memo + this.buildPublicEvent = memoize((context, eventDef, eventInstance) => new EventImpl(context, eventDef, eventInstance)); + this.handleEl = (el) => { + this.el = el; + if (el) { + setElEventRange(el, this.props.eventRange); + } + }; + } + render() { + const { props, context } = this; + const { eventRange } = props; + const { options } = context; + const eventUi = eventRange.ui; + const eventApi = this.buildPublicEvent(context, eventRange.def, eventRange.instance); + const subcontentRenderProps = { + event: eventApi, + isNarrow: props.isNarrow || false, + isShort: props.isShort || false, + }; + const renderProps = { + event: eventApi, + view: context.viewApi, + timeText: '', // never display time + color: eventUi.color || options.backgroundEventColor, + contrastColor: eventUi.contrastColor, + isDraggable: false, + isStartResizable: false, + isEndResizable: false, + isMirror: false, + isStart: props.isStart, + isEnd: props.isEnd, + isFirst: false, + isLast: false, + isPast: props.isPast, + isFuture: props.isFuture, + isToday: props.isToday, + isSelected: false, + isDragging: false, + isResizing: false, + isInteractive: false, + level: 0, + isNarrow: props.isNarrow || false, + isShort: props.isShort || false, + timeClass: '', // never display time + titleClass: generateClassName(options.backgroundEventTitleClass, subcontentRenderProps), + options: { eventOverlap: Boolean(options.eventOverlap) }, + }; + // does not include backgroundEventClass.. added below + const outerClassName = joinClassNames(eventUi.className, classNames.fill, classNames.internalEvent, classNames.internalBgEvent, props.isVertical ? classNames.flexCol : classNames.flexRow); + const innerClassName = joinClassNames(generateClassName(options.backgroundEventInnerClass, renderProps), classNames.liquid); + return (u$1(ContentContainer, { tag: 'div', className: outerClassName, style: { + '--fc-event-color': renderProps.color, + '--fc-event-contrast-color': renderProps.contrastColor, + }, defaultGenerator: renderInnerContent$1, elRef: this.handleEl, renderProps: renderProps, generatorName: "backgroundEventContent", customGenerator: options.backgroundEventContent, classNameGenerator: options.backgroundEventClass, didMount: options.backgroundEventDidMount, willUnmount: options.backgroundEventWillUnmount, children: (InnerContent) => (u$1(InnerContent, { tag: 'div', className: innerClassName })) })); + } + componentDidUpdate(prevProps) { + if (this.el && this.props.eventRange !== prevProps.eventRange) { + setElEventRange(this.el, this.props.eventRange); + } + } + } + function renderInnerContent$1(props) { + let { title } = props.event; + return title && (u$1("div", { className: props.titleClass, children: props.event.title })); + } + // Other types of fills + // ------------------------------------------------------------------------------------------------- + function renderFill(fillType, options) { + return (u$1("div", { className: joinClassNames(fillType === 'non-business' ? options.nonBusinessHoursClass : + fillType === 'highlight' ? options.highlightClass : undefined, classNames.fill) })); + } + + const SPACE_FROM_VIEWPORT = 10; + const ROW_BORDER_WIDTH = 1; + class MorePopover extends DateComponent { + constructor() { + super(...arguments); + // memo + this.getDateMeta = memoize(getDateMeta); + this.closeRef = M$1(); + this.focusStartRef = M$1(); + this.focusEndRef = M$1(); + this.handleRootEl = (rootEl) => { + this.rootEl = rootEl; + if (rootEl) { + this.context.registerInteractiveComponent(this, { + el: rootEl, + useEventCenter: false, + }); + } + else { + this.context.unregisterInteractiveComponent(this); + } + }; + // Triggered when the user clicks *anywhere* in the document, for the autoHide feature + this.handleDocumentMouseDown = (ev) => { + // only hide the popover if the click happened outside the popover + const target = getEventTargetViaRoot(ev); + if (!this.rootEl.contains(target)) { + this.handleClose(); + } + }; + this.handleDocumentKeyDown = (ev) => { + if (ev.key === 'Escape') { + this.handleClose(); + } + }; + // for many different close techniques + // cannot accept params because might receive a browser Event + this.handleClose = () => { + let { onClose } = this.props; + if (onClose) { + onClose(); + } + }; + } + render() { + let { props, context } = this; + let { options, dateEnv, viewApi } = context; + let { startDate, todayRange, dateProfile } = props; + let dateMeta = this.getDateMeta(startDate, dateEnv, dateProfile, todayRange); + let textParts = dateEnv.formatToParts(startDate, options.popoverFormat); + let text = joinDateTimeFormatParts(textParts); + const dayHeaderRenderProps = { + ...dateMeta, + isMajor: false, + isNarrow: false, + isSticky: false, + inPopover: true, + level: 0, + hasNavLink: false, + text, + textParts, + get weekdayText() { return findWeekdayText(textParts); }, + get dayNumberText() { return findDayNumberText(textParts); }, + view: viewApi, + // TODO: should know about the resource! + }; + const dayCellRenderProps = { + ...dateMeta, + isMajor: false, + isNarrow: false, + inPopover: true, + hasNavLink: false, + get weekdayText() { return findWeekdayText(textParts); }, + get dayNumberText() { return findDayNumberText(textParts); }, + get monthText() { return findMonthText(textParts); }, + view: viewApi, + text: '', + textParts: [], + options: { businessHours: Boolean(options.businessHours) }, + }; + const fullDateStr = formatDayString(startDate); + /* + TODO: DRY with TimelineHeaderCell + */ + const { dayHeaderAlign } = options; + const align = typeof dayHeaderAlign === 'function' + ? dayHeaderAlign({ level: 0, inPopover: true, isNarrow: false }) + : dayHeaderAlign; + const isRtl = computeElIsRtl(props.alignEl); + return $(u$1("div", { "data-date": fullDateStr, id: props.id, role: 'dialog', "aria-labelledby": props.titleId, className: joinClassNames(options.popoverClass, classNames.flexCol, classNames.popoverZ, classNames.abs, classNames.borderBoxRoot, classNames.internalPopover), style: { + // positioning is mutated directly in updateSize, HOWEVER, we don't want popover to start + // low on screen because might cause unnecessary scrollbars + top: 0, + left: 0, + }, + // HACK because of portal + dir: isRtl ? 'rtl' : undefined, "data-color-scheme": options.colorScheme || undefined, ref: this.handleRootEl, children: [u$1("div", { tabIndex: 0, style: { outline: 'none' }, ref: this.focusStartRef }), u$1("div", { className: joinClassNames(generateClassName(options.dayHeaderClass, dayHeaderRenderProps), classNames.flexCol, classNames.borderOnlyB, align === 'center' ? classNames.alignCenter : + align === 'end' ? classNames.alignEnd : + classNames.alignStart), children: [u$1("div", { children: u$1(ContentContainer, { tag: "div", attrs: { + id: props.titleId, + // NOTE: more-popover never has nav-links + }, generatorName: "dayHeaderContent", renderProps: dayHeaderRenderProps, customGenerator: options.dayHeaderContent, defaultGenerator: renderText, classNameGenerator: options.dayHeaderInnerClass, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }) }), u$1(ContentContainer, { tag: 'button', attrs: { + 'aria-label': options.closeHint, + ...createAriaClickAttrs(this.handleClose) + }, elRef: this.closeRef, className: joinClassNames(options.popoverCloseClass, classNames.flexRow, classNames.cursorPointer), renderProps: {}, customGenerator: options.popoverCloseContent, generatorName: 'popoverCloseContent' })] }), u$1("div", { className: joinClassNames(generateClassName(options.dayCellClass, dayCellRenderProps), classNames.flexCol, classNames.borderNone), children: u$1("div", { className: generateClassName(options.dayCellInnerClass, dayCellRenderProps), children: props.children }) }), u$1("div", { tabIndex: 0, style: { outline: 'none' }, ref: this.focusEndRef })] }), getAppendableRoot(props.alignEl)); + } + queryHit(isRtl, positionLeft, positionTop, elWidth, elHeight) { + let { rootEl, props } = this; + if (positionLeft >= 0 && positionLeft < elWidth && + positionTop >= 0 && positionTop < elHeight) { + return { + dateProfile: props.dateProfile, + dateSpan: { + allDay: !props.forceTimed, + range: { + start: props.startDate, + end: props.endDate, + }, + ...props.dateSpanProps, + }, + getDayEl: () => rootEl, + rect: { + left: 0, + top: 0, + right: elWidth, + bottom: elHeight, + }, + layer: 1, // important when comparing with hits from other components + }; + } + return null; + } + componentDidMount() { + document.addEventListener('mousedown', this.handleDocumentMouseDown); + document.addEventListener('keydown', this.handleDocumentKeyDown); + this.focusStartRef.current.addEventListener('focus', this.handleClose); + this.focusEndRef.current.addEventListener('focus', this.handleClose); + this.closeRef.current.focus({ preventScroll: true }); + this.updateSize(); + } + componentWillUnmount() { + document.removeEventListener('mousedown', this.handleDocumentMouseDown); + document.removeEventListener('keydown', this.handleDocumentKeyDown); + this.focusStartRef.current.removeEventListener('focus', this.handleClose); + this.focusEndRef.current.removeEventListener('focus', this.handleClose); + } + updateSize() { + let { alignEl, alignParentTop } = this.props; + let { rootEl: popoverEl } = this; + const isRtl = computeElIsRtl(alignEl); + // position relative to viewport + const alignmentRect = computeClippedClientRect(alignEl); + if (alignmentRect) { + let popoverDims = popoverEl.getBoundingClientRect(); + // position relative to viewport + let popoverVPTop = alignParentTop + // HACK: subtract 1 for DayGrid, which has borders on row-bottom. Only view that uses alignParentTop + ? alignEl.closest(alignParentTop).getBoundingClientRect().top - ROW_BORDER_WIDTH + : alignmentRect.top; + let popoverVPLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left; + // constrain + popoverVPTop = Math.max(popoverVPTop, SPACE_FROM_VIEWPORT); + popoverVPLeft = Math.min(popoverVPLeft, document.documentElement.clientWidth - SPACE_FROM_VIEWPORT - popoverDims.width); + popoverVPLeft = Math.max(popoverVPLeft, SPACE_FROM_VIEWPORT); + const { offsetParent } = popoverEl; + // final popover position, relative to offsetParent + let top; + let left; + // TODO: account for RTL + if (!offsetParent || offsetParent === document.body) { + top = popoverVPTop + window.scrollY; + left = popoverVPLeft + window.scrollX; + } + else { + const offsetParentRect = offsetParent.getBoundingClientRect(); + top = popoverVPTop - offsetParentRect.top + offsetParent.scrollTop; + left = popoverVPLeft - offsetParentRect.left + offsetParent.scrollLeft; + } + applyStyle(popoverEl, { top, left }); + } + } + } + // TODO: DRY + function renderText(renderProps) { + return renderProps.text; + } + + function doCoordRangesIntersect(r0, r1) { + return r0.end > r1.start && r0.start < r1.end; + } + function intersectCoordRanges(r0, r1) { + const start = Math.max(r0.start, r1.start); + const end = Math.min(r0.end, r1.end); + if (start < end) { + return { + start, + end, + isStart: r0.isStart && start === r0.start, + isEnd: r0.isEnd && end === r0.end, + }; + } + } + function joinCoordRanges(r0, r1) { + return { + start: Math.min(r0.start, r1.start), + end: Math.max(r0.end, r1.end), + }; + } + function getCoordRangeEnd(r) { + return r.end; + } + // { eventRange } + // ------------------------------------------------------------------------------------------------- + function computeEarliestStart(segs) { + return segs.reduce(pickEarliestStart).eventRange.range.start; + } + function computeLatestEnd(segs) { + return segs.reduce(pickLatestEnd).eventRange.range.end; + } + function pickEarliestStart(r0, r1) { + return r0.eventRange.range.start < r1.eventRange.range.start ? r0 : r1; + } + function pickLatestEnd(r0, r1) { + return r0.eventRange.range.end > r1.eventRange.range.end ? r0 : r1; + } + + /* + IMPORTANT: caller is responsible for injecting moreLinkInnerClass, + either on root `classNames` or within inner element + */ + class MoreLinkContainer extends BaseComponent { + constructor() { + super(...arguments); + this.state = { + isPopoverOpen: false, + }; + this.handleLinkEl = (linkEl) => { + this.linkEl = linkEl; + if (this.props.elRef) { + setRef(this.props.elRef, linkEl); + } + }; + this.handleClick = (ev) => { + let { props, context } = this; + let { dateEnv, options } = context; + let { moreLinkClick } = options; + let date = computeRange(props).start; + function buildPublicSeg(seg) { + let { def, instance, range } = seg.eventRange; + return { + event: new EventImpl(context, def, instance), + start: dateEnv.toDate(range.start), + end: dateEnv.toDate(range.end), + isStart: seg.isStart, + isEnd: seg.isEnd, + }; + } + if (typeof moreLinkClick === 'function') { + moreLinkClick = moreLinkClick({ + date: dateEnv.toDate(date), + allDay: Boolean(props.allDayDate), + allSegs: props.segs.map(buildPublicSeg), + hiddenSegs: props.hiddenSegs.map(buildPublicSeg), + jsEvent: ev, + view: context.viewApi, + }); + } + if (!moreLinkClick || moreLinkClick === 'popover') { + this.setState({ isPopoverOpen: true }); + } + else if (typeof moreLinkClick === 'string') { // a view name + context.calendarApi.zoomTo(date, moreLinkClick); + } + }; + this.handlePopoverClose = () => { + if (this.linkEl) { // was null sometimes when initiating drag-n-drop would hide the popover + this.linkEl.focus(); + } + this.setState({ isPopoverOpen: false }); + }; + } + render() { + let { props, state } = this; + return (u$1(ViewContextType.Consumer, { children: (context) => { + let { viewApi, options, calendarApi, baseId } = context; + let { moreLinkText } = options; + let moreCnt = props.hiddenSegs.length; + let range = computeRange(props); + let popoverId = baseId + 'popover-' + range.start.toISOString(); + let numericText = `+${moreCnt}`; // TODO: offer hook or i18n? + let longText = typeof moreLinkText === 'function' // TODO: eventually use formatWithOrdinals + ? moreLinkText.call(calendarApi, moreCnt) + : `${numericText} ${moreLinkText}`; + let hint = formatWithOrdinals(options.moreLinkHint, [moreCnt], longText); + let renderProps = { + num: moreCnt, + numericText, + longText, + text: (props.isMicro || props.display === 'column') ? numericText : longText, + isNarrow: props.isNarrow, + view: viewApi, + }; + return (u$1(S, { children: [Boolean(moreCnt) && (u$1(ContentContainer, { tag: 'div', elRef: this.handleLinkEl, className: joinClassNames(generateClassName(// will added to moreLinkClass + props.display === 'row' + ? options.rowMoreLinkClass // row + : options.columnMoreLinkClass, // column + renderProps), props.className, props.display === 'row' + ? classNames.flexRow + : classNames.flexCol, classNames.internalMoreLink, classNames.cursorPointer), style: props.style, attrs: { + ...props.attrs, + ...createAriaClickAttrs(this.handleClick), + title: hint, + 'role': 'button', + 'aria-haspopup': 'dialog', + 'aria-expanded': state.isPopoverOpen, + 'aria-controls': state.isPopoverOpen ? popoverId : undefined, + }, renderProps: renderProps, generatorName: "moreLinkContent", customGenerator: options.moreLinkContent, defaultGenerator: renderMoreLinkText, classNameGenerator: options.moreLinkClass, didMount: options.moreLinkDidMount, willUnmount: options.moreLinkWillUnmount, children: (InnerContent) => (u$1(InnerContent, { tag: 'div', className: joinClassNames(generateClassName(options.moreLinkInnerClass, renderProps), generateClassName(props.display === 'row' + ? options.rowMoreLinkInnerClass // row + : options.columnMoreLinkInnerClass, // column + renderProps), props.display === 'row' + ? classNames.stickyS + : classNames.stickyT) })) })), state.isPopoverOpen && (u$1(MorePopover, { id: popoverId, titleId: popoverId + '-title', startDate: range.start, endDate: range.end, dateProfile: props.dateProfile, todayRange: props.todayRange, dateSpanProps: props.dateSpanProps, alignEl: props.alignElRef ? + props.alignElRef.current : + this.linkEl, alignParentTop: props.alignParentTop, forceTimed: props.forceTimed, onClose: this.handlePopoverClose, children: props.popoverContent() }))] })); + } })); + } + } + function renderMoreLinkText(props) { + return props.text; + } + function computeRange(props) { + if (props.allDayDate) { + return { + start: props.allDayDate, + end: addDays(props.allDayDate, 1), + }; + } + return { + start: computeEarliestStart(props.hiddenSegs), + end: computeLatestEnd(props.hiddenSegs), + }; + } + + const DEFAULT_TABLE_EVENT_TIME_FORMAT = createFormatter({ + hour: 'numeric', + minute: '2-digit', + omitZeroMinute: true, + meridiem: 'narrow', + }); + function hasListItemDisplay(seg) { + let { display } = seg.eventRange.ui; + return display === 'list-item' || (display === 'auto' && + !seg.eventRange.def.allDay && + (seg.end - seg.start) === 1 && // single-day + seg.isStart && // " + seg.isEnd // " + ); + } + + class DayGridMoreLink extends BaseComponent { + render() { + let { props } = this; + return (u$1(MoreLinkContainer, { display: 'row', className: props.className, isNarrow: props.isNarrow, isMicro: props.isMicro, dateProfile: props.dateProfile, todayRange: props.todayRange, allDayDate: props.allDayDate, segs: props.segs, hiddenSegs: props.hiddenSegs, alignElRef: props.alignElRef, alignParentTop: props.alignParentTop, dateSpanProps: props.dateSpanProps, popoverContent: () => (u$1(S, { children: props.segs.map((seg) => { + let { eventRange } = seg; + let { instanceId } = eventRange.instance; + let isDragging = Boolean(props.eventDrag && props.eventDrag.affectedInstances[instanceId]); + let isResizing = Boolean(props.eventResize && props.eventResize.affectedInstances[instanceId]); + let isInvisible = isDragging || isResizing; + return (u$1("div", { style: { + visibility: isInvisible ? 'hidden' : undefined, + }, children: u$1(StandardEvent, { display: hasListItemDisplay(seg) ? 'list-item' : 'row', eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isMirror: false, isSelected: instanceId === props.eventSelection, defaultTimeFormat: DEFAULT_TABLE_EVENT_TIME_FORMAT, defaultDisplayEventEnd: false, ...getEventRangeMeta(eventRange, props.todayRange) }) }, instanceId)); + }) })) })); + } + } + + class DayGridCell extends DateComponent { + constructor() { + super(...arguments); + // memo + this.getDateMeta = memoize(getDateMeta); + this.refineRenderProps = memoizeObjArg(refineRenderProps); + // ref + this.rootElRef = M$1(); + this.handleBodyEl = (bodyEl) => { + if (this.disconnectBodyHeight) { + this.disconnectBodyHeight(); + this.disconnectBodyHeight = undefined; + setRef(this.props.headerHeightRef, null); + setRef(this.props.mainHeightRef, null); + } + if (bodyEl) { + // we want to fire on ANY size change, because we do more advanced stuff + this.disconnectBodyHeight = watchSize(bodyEl, (_bodyWidth, bodyHeight) => { + if (this._isUnmounting) + return; + const { props } = this; + const mainRect = bodyEl.getBoundingClientRect(); + const rootRect = this.rootElRef.current.getBoundingClientRect(); + const headerHeight = mainRect.top - rootRect.top; + if (!isDimsEqual(this.headerHeight, headerHeight)) { + this.headerHeight = headerHeight; + setRef(props.headerHeightRef, headerHeight); + } + if (props.fgLiquidHeight) { + setRef(props.mainHeightRef, bodyHeight); + } + }); + } + }; + } + render() { + let { props, context } = this; + let { options, dateEnv } = context; + // TODO: memoize this + const isMonthStart = props.showDayNumber && + shouldDisplayMonthStart(props.date, props.dateProfile.currentRange, dateEnv); + const dateMeta = this.getDateMeta(props.date, dateEnv, props.dateProfile, props.todayRange); + const baseClassName = joinClassNames(props.borderStart ? classNames.borderOnlyS : classNames.borderNone, props.width != null ? '' : classNames.liquid, classNames.flexCol, classNames.noMargin, classNames.noPadding); + const hasNavLink = options.navLinks; + const renderProps = this.refineRenderProps({ + date: props.date, + isMajor: props.isMajor, + isNarrow: props.isNarrow, + dateMeta: dateMeta, + hasLabel: props.showDayNumber, + hasMonthLabel: isMonthStart, + hasNavLink, + renderProps: props.renderProps, + viewApi: context.viewApi, + dateEnv: context.dateEnv, + monthStartFormat: options.monthStartFormat, + dayCellFormat: options.dayCellFormat, + businessHours: Boolean(options.businessHours), + }); + if (dateMeta.isDisabled) { + return (u$1("div", { role: 'gridcell', "aria-disabled": true, className: joinClassNames(generateClassName(options.dayCellClass, renderProps), props.className, baseClassName), style: { + width: props.width + } })); + } + const fullDateStr = buildDateStr(context, props.date); + return (u$1(ContentContainer, { tag: "div", elRef: this.rootElRef, className: joinClassNames(props.className, baseClassName), attrs: { + ...props.attrs, + role: 'gridcell', + 'aria-label': fullDateStr, + ...(renderProps.isToday ? { 'aria-current': 'date' } : {}), + 'data-date': formatDayString(props.date), + }, style: { + width: props.width, + }, renderProps: renderProps, generatorName: "dayCellTopContent" // !!! for top + , customGenerator: options.dayCellTopContent /* !!! for top */, defaultGenerator: renderTopInner, classNameGenerator: options.dayCellClass, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount, children: (InnerContent) => (u$1(S, { children: [u$1("div", { className: joinClassNames(classNames.rel, // puts it above bg-fills, which are positioned on TOP of this component :| + generateClassName(options.dayCellTopClass, renderProps)), children: props.showDayNumber && (u$1(InnerContent // the dayCellTopContent + , { tag: 'div', attrs: hasNavLink + ? buildNavLinkAttrs(context, props.date, undefined, fullDateStr) + : { 'aria-hidden': true } // label already on cell + , className: generateClassName(options.dayCellTopInnerClass, renderProps) })) }), u$1("div", { className: joinClassNames(classNames.flexCol, props.fgLiquidHeight ? classNames.liquid : classNames.grow), ref: this.handleBodyEl, children: [u$1("div", { className: generateClassName(options.dayCellInnerClass, renderProps), style: { minHeight: props.fgHeight }, children: props.fg }), u$1(DayGridMoreLink, { className: classNames.rel, allDayDate: props.date, segs: props.segs, hiddenSegs: props.hiddenSegs, alignElRef: this.rootElRef, alignParentTop: props.showDayNumber + ? '[role=row]' + : `.${classNames.internalView}`, dateSpanProps: props.dateSpanProps, dateProfile: props.dateProfile, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange, isNarrow: props.isNarrow, isMicro: props.isMicro })] }), u$1("div", { className: joinClassNames(classNames.rel, // puts it above bg-fills + generateClassName(options.dayCellBottomClass, renderProps)) })] })) })); + } + componentDidMount() { + this._isUnmounting = false; + } + componentWillUnmount() { + this._isUnmounting = true; + } + } + // Utils + // ------------------------------------------------------------------------------------------------- + function renderTopInner(props) { + return props.text || u$1(S, { children: "\u00A0" }); // TODO: DRY? + } + function shouldDisplayMonthStart(date, currentRange, dateEnv) { + const { start: currentStart, end: currentEnd } = currentRange; + const currentEndIncl = addMs(currentEnd, -1); + const currentFirstYear = dateEnv.getYear(currentStart); + const currentFirstMonth = dateEnv.getMonth(currentStart); + const currentLastYear = dateEnv.getYear(currentEndIncl); + const currentLastMonth = dateEnv.getMonth(currentEndIncl); + // spans more than one month? + return !(currentFirstYear === currentLastYear && currentFirstMonth === currentLastMonth) && + Boolean( + // first date in current view? + date.valueOf() === currentStart.valueOf() || + // a month-start that's within the current range? + (dateEnv.getDay(date) === 1 && date.valueOf() < currentEnd.valueOf())); + } + function refineRenderProps(raw) { + let { date, dateEnv, hasLabel, hasMonthLabel, hasNavLink, businessHours } = raw; + let textParts = []; + let text = ''; + if (hasLabel) { + textParts = dateEnv.formatToParts(date, hasMonthLabel ? raw.monthStartFormat : raw.dayCellFormat); + text = joinDateTimeFormatParts(textParts); + } + return { + ...raw.dateMeta, + ...raw.renderProps, + text, + textParts, + isMajor: raw.isMajor, + isNarrow: raw.isNarrow, + inPopover: false, + hasNavLink, + get weekdayText() { return findWeekdayText(textParts); }, + get dayNumberText() { return findDayNumberText(textParts); }, + get monthText() { return findMonthText(textParts); }, + options: { businessHours }, + view: raw.viewApi, + }; + } + + class SegHierarchy { + constructor(segs, getSegThickness = (seg) => { + return 1; + }, strictOrder = false, // HACK + maxCoord, maxDepth, hiddenConsumes = false, // hidden segs also hide the touchingPlacement? + allowSlicing = false) { + this.getSegThickness = getSegThickness; + this.strictOrder = strictOrder; + this.maxCoord = maxCoord; + this.maxDepth = maxDepth; + this.hiddenConsumes = hiddenConsumes; + this.allowSlicing = allowSlicing; + this.placementsByLevel = []; + this.levelCoords = []; // parallel with placementsByLevel + this.hiddenSegs = []; + for (const seg of segs) { + this.insertSeg(seg, this.getSegThickness(seg)); + } + } + insertSeg(seg, segThickness, isSlice) { + if (segThickness != null) { + const insertion = this.findInsertion(seg, segThickness); + if (this.isInsertionValid(insertion, segThickness)) { + this.insertSegAt(seg, insertion, segThickness, isSlice); + } + else { + const { touchingPlacement } = insertion; + // is there a touching-seg? + if (touchingPlacement) { + // should we hide or reslice touchingPlacement? + if (this.hiddenConsumes && !touchingPlacement.isZombie) { + touchingPlacement.isZombie = true; // edit in-place + this.hiddenSegs.push(touchingPlacement); + if (this.allowSlicing) { + const newSeg = Object.assign({}, touchingPlacement); // copy + // slice touchingPlacement in-place + Object.assign(touchingPlacement, intersectCoordRanges(touchingPlacement, seg)); + touchingPlacement.isSlice = true; + // try to reinsert touchingPlacement's seg + this.splitSeg(newSeg, touchingPlacement.thickness, touchingPlacement); + } + } + // record seg as hidden, potentially split by touchingPlacement + if (this.allowSlicing) { + this.hiddenSegs.push({ + ...seg, + ...intersectCoordRanges(seg, touchingPlacement), + }); + this.splitSeg(seg, segThickness, touchingPlacement); + } + else { + this.hiddenSegs.push(seg); + } + // not touching anything + } + else { + this.hiddenSegs.push(seg); + } + } + } + } + /* + TODO: inline? + */ + isInsertionValid(insertion, thickness) { + return (this.maxCoord == null || insertion.levelCoord + thickness <= this.maxCoord) && + (this.maxDepth == null || insertion.depth < this.maxDepth); + } + /* + Does not add the portion that intersects with barrier to hiddenSegs + */ + splitSeg(seg, segThickness, barrier) { + // any leftover seg on the start-side of the barrier? + if (seg.start < barrier.start) { + this.insertSeg({ ...seg, end: barrier.start, isEnd: false }, segThickness, + /* isSlice = */ true); + } + // any leftover seg on the end-side of the barrier? + if (seg.end > barrier.end) { + this.insertSeg({ ...seg, start: barrier.end, isStart: false }, segThickness, + /* isSlice = */ true); + } + } + /* + TODO: inline? + */ + insertSegAt(seg, insertion, segThickness, isSlice) { + const placement = { + ...seg, + thickness: segThickness, + depth: insertion.depth, + isSlice: isSlice || seg.isSlice || false, + isZombie: false, + }; + if (insertion.lateralIndex === -1) { + // create a new level + insertAt(this.placementsByLevel, insertion.levelIndex, [placement]); + insertAt(this.levelCoords, insertion.levelIndex, insertion.levelCoord); + } + else { + // insert into existing level + insertAt(this.placementsByLevel[insertion.levelIndex], insertion.lateralIndex, placement); + } + } + /* + Ignores limits + */ + findInsertion(seg, segThickness) { + let { placementsByLevel, levelCoords } = this; + let levelCnt = placementsByLevel.length; + let candidateCoord = 0; // a tentative levelCoord for seg's placement + let touchingPlacement; + let touchingLevelIndex; + let depth = 0; + // iterate through existing levels + for (let currentLevelIndex = 0; currentLevelIndex < levelCnt; currentLevelIndex += 1) { + const currentLevelCoord = levelCoords[currentLevelIndex]; + // if the current level has cleared seg's bottom coord, we have found a good empty space and can stop. + // if strictOrder, keep finding more lateral intersections. + if (!this.strictOrder && currentLevelCoord >= candidateCoord + segThickness) { + break; + } + let currentLevelSegs = placementsByLevel[currentLevelIndex]; + let currentSeg; + // finds the first possible entry that seg could intersect with + let [searchIndex, isExact] = binarySearch(currentLevelSegs, seg.start, getCoordRangeEnd); // find first entry after seg's end + let lateralIndex = searchIndex + isExact; // if exact match (which doesn't collide), go to next one + // loop through entries that horizontally intersect + while ((currentSeg = currentLevelSegs[lateralIndex]) && // but not past the whole entry list + currentSeg.start < seg.end // and not entirely past seg + ) { + let currentEntryBottom = currentLevelCoord + currentSeg.thickness; + // intersects into the top of the candidate? + if (currentEntryBottom > candidateCoord) { + // push it downward so doesn't 'vertically' intersect anymore + candidateCoord = currentEntryBottom; + // tentatively record as touching + touchingPlacement = currentSeg; + touchingLevelIndex = currentLevelIndex; + } + // does current entry butt up against top of candidate? + // will obviously happen if just intersected, but can also happen if pushed down previously + // because intersected with a sibling + // TODO: after automated tests hooked up, see if these gate is unnecessary, + // we might just be able to do this for ALL intersecting currentEntries (this whole loop) + if (currentEntryBottom === candidateCoord) { + // accumulate the highest possible depth of the currentLevelSegs that butt up + depth = Math.max(depth, currentSeg.depth + 1); + } + lateralIndex += 1; + } + } + // the destination level will be after touchingPlacement's level. find it + // TODO: can reuse work from above? + let destLevelIndex = 0; + if (touchingPlacement) { + destLevelIndex = touchingLevelIndex + 1; + while (destLevelIndex < levelCnt && levelCoords[destLevelIndex] < candidateCoord) { + destLevelIndex += 1; + } + } + // if adding to an existing level, find where to insert + // TODO: can reuse work from above? + let destLateralIndex = -1; + if (destLevelIndex < levelCnt && levelCoords[destLevelIndex] === candidateCoord) { + [destLateralIndex] = binarySearch(placementsByLevel[destLevelIndex], seg.end, getCoordRangeEnd); + } + return { + touchingPlacement, + levelCoord: candidateCoord, + levelIndex: destLevelIndex, + lateralIndex: destLateralIndex, + depth, + }; + } + traverseSegs(handler) { + const { placementsByLevel, levelCoords } = this; + for (let i = 0; i < placementsByLevel.length; i++) { + const placements = placementsByLevel[i]; + const levelCoord = levelCoords[i]; + for (const placement of placements) { + if (!placement.isZombie) { + handler(placement, levelCoord); + } + } + } + } + } + /* + Returns groups with entries sorted by input order + */ + function groupIntersectingSegs(segs) { + let mergedGroups = []; + for (let seg of segs) { + let filteredGroups = []; + let hungryGroup = { + segs: [seg], + start: seg.start, + end: seg.end, + }; + for (let mergedGroup of mergedGroups) { + if (doCoordRangesIntersect(mergedGroup, hungryGroup)) { + hungryGroup = { + ...joinCoordRanges(mergedGroup, hungryGroup), + segs: mergedGroup.segs.concat(hungryGroup.segs) // keep preexisting mergedGroup's items first. maintains order + }; + } + else { + filteredGroups.push(mergedGroup); + } + } + filteredGroups.push(hungryGroup); + mergedGroups = filteredGroups; + } + return mergedGroups.map((mergedGroup) => { + return { + key: buildIsoString(computeEarliestStart(mergedGroup.segs)), + ...mergedGroup + }; + }); + } + // General Utils + // ------------------------------------------------------------------------------------------------- + function insertAt(arr, index, item) { + arr.splice(index, 0, item); + } + function binarySearch(a, searchVal, getItemVal) { + let startIndex = 0; + let endIndex = a.length; // exclusive + if (!endIndex || searchVal < getItemVal(a[startIndex])) { // no items OR before first item + return [0, 0]; + } + if (searchVal > getItemVal(a[endIndex - 1])) { // after last item + return [endIndex, 0]; + } + while (startIndex < endIndex) { + let middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2); + let middleVal = getItemVal(a[middleIndex]); + if (searchVal < middleVal) { + endIndex = middleIndex; + } + else if (searchVal > middleVal) { + startIndex = middleIndex + 1; + } + else { // equal! + return [middleIndex, 1]; + } + } + return [startIndex, 0]; + } + + function computeFgSegVerticals$1(segs, segHeightMap, cells, maxHeight, strictOrder, allowSlicing = true, dayMaxEvents, dayMaxEventRows) { + let maxCoord; + let maxDepth; + let hiddenConsumes; + if (dayMaxEvents === true || dayMaxEventRows === true) { + maxCoord = maxHeight; + hiddenConsumes = true; + } + else if (typeof dayMaxEvents === 'number') { + maxDepth = dayMaxEvents; + hiddenConsumes = false; + } + else if (typeof dayMaxEventRows === 'number') { + maxDepth = dayMaxEventRows; + hiddenConsumes = true; + } + // NOTE: visibleSegsMap and hiddenSegMap map NEVER overlap for a given event + // once a seg has a height, the combined potentially-sliced segs will comprise the entire span of the seg + // if a seg does not have a height yet, it won't be inserted into either visibleSegsMap/hiddenSegMap + const visibleSegMap = new Map(); + const hiddenSegMap = new Map(); + const segTops = new Map(); + const isSlicedMap = new Map(); + let hierarchy = new SegHierarchy(segs, (seg) => segHeightMap.get(getEventPartKey(seg)), strictOrder, maxCoord, maxDepth, hiddenConsumes, allowSlicing); + hierarchy.traverseSegs((seg, segTop) => { + addToSegMap(visibleSegMap, seg); + segTops.set(getEventPartKey(seg), segTop); + if (seg.isSlice) { + isSlicedMap.set(seg.eventRange, true); + } + }); + for (const hiddenSeg of hierarchy.hiddenSegs) { + addToSegMap(hiddenSegMap, hiddenSeg); // hidden main segs + } + // recompute tops while considering slices + // portions of these slices might be added to hiddenSegMap + if (isSlicedMap.size) { + segTops.clear(); + hierarchy = new SegHierarchy(compileSegMap(segs, visibleSegMap), (seg) => segHeightMap.get(getEventPartKey(seg)), strictOrder, maxCoord, maxDepth, hiddenConsumes); + hierarchy.traverseSegs((seg, segTop) => { + segTops.set(getEventPartKey(seg), segTop); // newly-hidden main segs and slices + }); + for (const hiddenSeg of hierarchy.hiddenSegs) { + addToSegMap(hiddenSegMap, hiddenSeg); + } + } + const segsByCol = []; + const hiddenSegsByCol = []; + const renderableSegsByCol = []; + const heightsByCol = []; + for (let col = 0; col < cells.length; col++) { + segsByCol.push([]); + hiddenSegsByCol.push([]); + renderableSegsByCol.push([]); + heightsByCol.push(0); + } + for (const seg of segs) { + const { eventRange } = seg; + const visibleSegs = visibleSegMap.get(eventRange) || []; + const hiddenSegs = hiddenSegMap.get(eventRange) || []; + const isSliced = isSlicedMap.get(eventRange) || false; + // add orig to renderable + renderableSegsByCol[seg.start].push(seg); + // add slices to renderable + if (isSliced) { + for (const visibleSeg of visibleSegs) { + renderableSegsByCol[visibleSeg.start].push(visibleSeg); + } + } + // accumulate segsByCol/heightsByCol for visible segs + for (const visibleSeg of visibleSegs) { + for (let col = visibleSeg.start; col < visibleSeg.end; col++) { + const slice = sliceSegForCol(visibleSeg, col); + segsByCol[col].push(slice); + } + const segKey = getEventPartKey(visibleSeg); + const segTop = segTops.get(segKey); + if (segTop != null) { // positioned? + const segHeight = segHeightMap.get(segKey); + for (let col = visibleSeg.start; col < visibleSeg.end; col++) { + heightsByCol[col] = Math.max(heightsByCol[col], segTop + segHeight); + } + } + } + // accumulate segsByCol/hiddenSegsByCol for hidden segs + for (const hiddenSeg of hiddenSegs) { + for (let col = hiddenSeg.start; col < hiddenSeg.end; col++) { + const slice = sliceSegForCol(hiddenSeg, col); + segsByCol[col].push(slice); + hiddenSegsByCol[col].push(slice); + } + } + } + return [ + segsByCol, // visible and hidden + hiddenSegsByCol, + renderableSegsByCol, + segTops, + heightsByCol, + ]; + } + // Utils + // ------------------------------------------------------------------------------------------------- + function addToSegMap(map, seg) { + let list = map.get(seg.eventRange); + if (!list) { + map.set(seg.eventRange, list = []); + } + list.push(seg); + } + /* + Ensures relative order of DayRowEventRange stays consistent with segs + */ + function compileSegMap(segs, segMap) { + const res = []; + for (const seg of segs) { + res.push(...(segMap.get(seg.eventRange) || [])); + } + return res; + } + + class DaySeriesModel { + constructor(range, dateProfileGenerator) { + let date = range.start; + let { end } = range; + let indices = []; + let dates = []; + let dayIndex = -1; + while (date < end) { // loop each day from start to end + if (dateProfileGenerator.isHiddenDay(date)) { + indices.push(dayIndex + 0.5); // mark that it's between indices + } + else { + dayIndex += 1; + indices.push(dayIndex); + dates.push(date); + } + date = addDays(date, 1); + } + this.dates = dates; + this.indices = indices; + this.cnt = dates.length; + } + sliceRange(range) { + let firstIndex = this.getDateDayIndex(range.start); // inclusive first index + let lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index + let clippedFirstIndex = Math.max(0, firstIndex); + let clippedLastIndex = Math.min(this.cnt - 1, lastIndex); + // deal with in-between indices + clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell + clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell + if (clippedFirstIndex <= clippedLastIndex) { + return { + start: clippedFirstIndex, + end: clippedLastIndex + 1, // make exclusive + isStart: firstIndex === clippedFirstIndex, + isEnd: lastIndex === clippedLastIndex, + }; + } + return null; + } + // Given a date, returns its chronolocial cell-index from the first cell of the grid. + // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets. + // If before the first offset, returns a negative number. + // If after the last offset, returns an offset past the last cell offset. + // Only works for *start* dates of cells. Will not work for exclusive end dates for cells. + getDateDayIndex(date) { + let { indices } = this; + let dayOffset = Math.floor(diffDays(this.dates[0], date)); + if (dayOffset < 0) { + return indices[0] - 1; + } + if (dayOffset >= indices.length) { + return indices[indices.length - 1] + 1; + } + return indices[dayOffset]; + } + } + + class DayTableModel { + constructor(daySeries, breakOnWeeks, dateEnv, majorUnit = '') { + this.dateEnv = dateEnv; + this.majorUnit = majorUnit; + let { dates } = daySeries; + let daysPerRow; + let firstDay; + let rowCount; + if (breakOnWeeks) { + // count columns until the day-of-week repeats + firstDay = dates[0].getUTCDay(); + for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) { + if (dates[daysPerRow].getUTCDay() === firstDay) { + break; + } + } + rowCount = Math.ceil(dates.length / daysPerRow); + } + else { + rowCount = 1; + daysPerRow = dates.length; + } + this.rowCount = rowCount; + this.colCount = daysPerRow; + this.daySeries = daySeries; + this.cellRows = this.buildCells(); + this.headerDates = this.buildHeaderDates(); + } + buildCells() { + let rows = []; + for (let row = 0; row < this.rowCount; row += 1) { + let cells = []; + for (let col = 0; col < this.colCount; col += 1) { + cells.push(this.buildCell(row, col)); + } + rows.push(cells); + } + return rows; + } + buildCell(row, col) { + let date = this.daySeries.dates[row * this.colCount + col]; + return { + key: date.toISOString(), + date, + isMajor: this.cellIsMajor(date), + }; + } + cellIsMajor(dateMarker) { + return this.majorUnit ? isMajorUnit(dateMarker, this.majorUnit, this.dateEnv) : false; + } + buildHeaderDates() { + let dates = []; + for (let col = 0; col < this.colCount; col += 1) { + dates.push(this.cellRows[0][col].date); + } + return dates; + } + sliceRange(range) { + let { colCount } = this; + let seriesSeg = this.daySeries.sliceRange(range); + let segs = []; + if (seriesSeg) { + const { start, end } = seriesSeg; + let index = start; + while (index < end) { + let row = Math.floor(index / colCount); + let nextIndex = Math.min((row + 1) * colCount, end); + segs.push({ + row, + start: index % colCount, + end: (nextIndex - 1) % colCount + 1, + isStart: seriesSeg.isStart && index === start, + isEnd: seriesSeg.isEnd && nextIndex === end, + }); + index = nextIndex; + } + } + return segs; + } + } + + function buildDayTableModel(dateProfile, dateProfileGenerator, dateEnv) { + const daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); + const breakOnWeeks = /year|month|week/.test(dateProfile.currentRangeUnit); + const majorUnit = !breakOnWeeks && computeMajorUnit(dateProfile, dateEnv); + // Exclude 'day': when cells are themselves days, all would match and the boundary + // distinction is meaningless (unlike timeline slots which can be sub-day). + return new DayTableModel(daySeries, breakOnWeeks, dateEnv, majorUnit !== 'day' ? majorUnit : undefined); + } + function computeColWidth(colCount, colMinWidth, viewportWidth) { + if (viewportWidth == null) { + return [undefined, undefined]; + } + const colTempWidth = viewportWidth / colCount; + if (colTempWidth < colMinWidth) { + return [colMinWidth * colCount, colMinWidth]; + } + return [viewportWidth, undefined]; + } + // Positioning + // ------------------------------------------------------------------------------------------------- + /* + TODO: handle hidden-days better. If current day is hidden day, scrolls to way bottom + */ + function computeTopFromDate(date, cellRows, rowHeightMap) { + let top = 0; + for (const cells of cellRows) { + const key = cells[0].key; + const start = cells[0].date; + const end = cells[cells.length - 1].date; // inclusive end + if (date >= start && date <= end) { + return top; + } + const rowHeight = rowHeightMap.get(key); + if (rowHeight == null) { + return; // denote unknown + } + top += rowHeight; + } + return top; + } + /* + FYI, `width` is not dependable for aligning completely to farside + */ + function computeHorizontalsFromSeg(seg, colWidth, colCount) { + let fromStart; + let fromEnd; + if (colWidth != null) { + fromStart = seg.start * colWidth; + fromEnd = (colCount - seg.end) * colWidth; + } + else { + const colWidthFrac = 1 / colCount; + fromStart = fracToCssDim(seg.start * colWidthFrac); + fromEnd = fracToCssDim(1 - seg.end * colWidthFrac); + } + return { insetInlineStart: fromStart, insetInlineEnd: fromEnd }; + } + function computeColFromPosition(positionLeft, elWidth, colWidth, colCount, isRtl) { + const realColWidth = colWidth != null ? colWidth : elWidth / colCount; + const colFromLeft = Math.floor(positionLeft / realColWidth); + const col = isRtl ? (colCount - colFromLeft - 1) : colFromLeft; + const left = colFromLeft * realColWidth; + const right = left + realColWidth; + return { col, left, right }; + } + function computeRowFromPosition(positionTop, cellRows, rowHeightMap) { + let row = 0; + let top = 0; + let bottom = 0; + for (const cells of cellRows) { + const key = cells[0].key; + top = bottom; + bottom = top + rowHeightMap.get(key); + if (positionTop < bottom) { + break; + } + row++; + } + return { row, top, bottom }; + } + // Hit Element + // ------------------------------------------------------------------------------------------------- + function getRowEl(rootEl, row) { + return rootEl.querySelectorAll('[role=row]')[row]; + } + function getCellEl(rowEl, col) { + return rowEl.querySelectorAll('[role=gridcell]')[col]; + } + // Header Formatting + // ------------------------------------------------------------------------------------------------- + const dayMicroWidth = 60; + const dayHeaderMicroFormat = createFormatter({ + weekday: 'narrow' + }); + function createDayHeaderFormatter(explicitFormat, datesRepDistinctDays, dateCnt) { + return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt); + } + // Computes a default column header formatting string if `colFormat` is not explicitly defined + function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) { + // if more than one week row, or if there are a lot of columns with not much space, + // put just the day numbers will be in each cell + if (!datesRepDistinctDays) { + return createFormatter({ weekday: 'short' }); // "Sat" + } + if (dayCnt > 1) { + return createFormatter({ + weekday: 'short', + weekdayJustify: 'start', + day: 'numeric', + omitCommas: true, + omitTrailing: true, + }); + } + return createFormatter({ + weekday: 'long', + weekdayJustify: 'start', + day: 'numeric', + omitCommas: true, + omitTrailing: true, + }); + } + + class DayGridEventHarness extends C { + constructor() { + super(...arguments); + // ref + this.rootElRef = M$1(); + } + render() { + const { props } = this; + return (u$1("div", { className: joinClassNames(props.className, classNames.abs), style: props.style, ref: this.rootElRef, children: props.children })); + } + componentDidMount() { + this._isUnmounting = false; + const rootEl = this.rootElRef.current; // TODO: make dynamic with useEffect + this.disconnectHeight = watchHeight(rootEl, (height) => { + if (this._isUnmounting) + return; + setRef(this.props.heightRef, height); + }); + } + componentWillUnmount() { + this._isUnmounting = true; + this.disconnectHeight(); + setRef(this.props.heightRef, null); + } + } + + const DEFAULT_WEEK_NUM_FORMAT$1 = createFormatter({ week: 'narrow' }); + class DayGridRow extends BaseComponent { + constructor() { + super(...arguments); + this.headerHeightRefMap = new RefMap(() => { + afterSize(this.handleSegPositioning); + }); + this.mainHeightRefMap = new RefMap(() => { + const fgLiquidHeight = this.props.dayMaxEvents === true || this.props.dayMaxEventRows === true; + if (fgLiquidHeight) { + afterSize(this.handleSegPositioning); + } + }); + this.segHeightRefMap = new RefMap(() => { + afterSize(this.handleSegPositioning); + }); + // memo + this.buildWeekNumberRenderProps = memoize(buildWeekNumberRenderProps); + this.handleRootEl = (rootEl) => { + this.rootEl = rootEl; + setRef(this.props.rootElRef, rootEl); + }; + this.handleSegPositioning = () => { + if (this._isUnmounting) + return; + this.forceUpdate(); + }; + } + render() { + const { props, context, headerHeightRefMap, mainHeightRefMap } = this; + const { cells } = props; + const { options } = context; + const weekDateMarker = props.cells[0].date; + const fgLiquidHeight = props.dayMaxEvents === true || props.dayMaxEventRows === true; + // TODO: memoize? sort all types of segs? + const fgEventSegs = sortEventSegs(props.fgEventSegs, options.eventOrder); + // TODO: memoize? + const [maxMainTop, minMainHeight] = this.computeFgDims(); // uses headerHeightRefMap/mainHeightRefMap + const [segsByCol, hiddenSegsByCol, renderableSegsByCol, segTops, simpleHeightsByCol] = computeFgSegVerticals$1(fgEventSegs, this.segHeightRefMap.current, cells, fgLiquidHeight ? minMainHeight : undefined, // if not defined in first run, will unlimited!? + options.eventOrderStrict, options.eventSlicing, props.dayMaxEvents, props.dayMaxEventRows); + const heightsByCol = []; + if (maxMainTop != null) { + let col = 0; + for (const cell of cells) { // uses headerHeightRefMap/maxMainTop/simpleHeightsByCol + const cellHeaderHeight = headerHeightRefMap.current.get(cell.key); + if (cellHeaderHeight != null) { + const extraFgHeight = maxMainTop - cellHeaderHeight; + heightsByCol.push(simpleHeightsByCol[col] + extraFgHeight); + } + else { + heightsByCol.push(undefined); + } + col++; + } + } + const highlightSegs = this.getHighlightSegs(); + const mirrorSegs = this.getMirrorSegs(); + const hasNavLink = options.navLinks; + const fullWeekStr = buildDateStr(context, weekDateMarker, 'week'); + const weekNumberRenderProps = this.buildWeekNumberRenderProps(weekDateMarker, context, props.cellIsNarrow, hasNavLink); + return (u$1("div", { role: props.role /* !!! */, "aria-label": props.role === 'row' // HACK + ? fullWeekStr + : undefined // can't have label on non-role div + , className: joinClassNames(options.dayRowClass, props.className, classNames.flexRow, classNames.rel, // origin for inlineWeekNumber? + classNames.isolate, (props.forPrint && props.basis !== undefined) && // basis implies siblings (must share height) + classNames.printSiblingRow), style: { + flexBasis: props.basis, + }, ref: this.handleRootEl, children: [(props.showWeekNumbers && !props.cellIsMicro) && (u$1(ContentContainer, { tag: 'div', attrs: { + ...(hasNavLink + ? buildNavLinkAttrs(context, weekDateMarker, 'week', fullWeekStr, /* isTabbable = */ false) + : {}), + 'role': undefined, // HACK: a 'link' role can't be child of 'row' role + 'aria-hidden': true, // HACK: never part of a11y tree because row already has label and role not allowed + }, + // put above all cells (TODO: put explicit z0 on each cell?) + className: classNames.z1, renderProps: weekNumberRenderProps, generatorName: "inlineWeekNumberContent", customGenerator: options.inlineWeekNumberContent, defaultGenerator: renderText$1, classNameGenerator: options.inlineWeekNumberClass, didMount: options.inlineWeekNumberDidMount, willUnmount: options.inlineWeekNumberWillUnmount })), this.renderFillSegs(props.businessHourSegs, 'non-business'), this.renderFillSegs(props.bgEventSegs, 'bg-event'), this.renderFillSegs(highlightSegs, 'highlight'), props.cells.map((cell, col) => { + const normalFgNodes = this.renderFgSegs(maxMainTop, renderableSegsByCol[col], segTops, props.todayRange, + /* isMirror = */ false); + return (u$1(DayGridCell, { dateProfile: props.dateProfile, todayRange: props.todayRange, date: cell.date, isMajor: cell.isMajor, showDayNumber: props.showDayNumbers, isNarrow: props.cellIsNarrow, isMicro: props.cellIsMicro, borderStart: Boolean(col), + // content + segs: segsByCol[col], hiddenSegs: hiddenSegsByCol[col], fgLiquidHeight: fgLiquidHeight, fg: (u$1(S, { children: normalFgNodes })), eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection, + // render hooks + renderProps: cell.renderProps, dateSpanProps: cell.dateSpanProps, attrs: cell.attrs, className: cell.className, + // dimensions + fgHeight: heightsByCol[col], width: props.colWidth, + // refs + headerHeightRef: headerHeightRefMap.createRef(cell.key), mainHeightRef: mainHeightRefMap.createRef(cell.key) }, cell.key)); + }), this.renderFgSegs(maxMainTop, mirrorSegs, segTops, props.todayRange, + /* isMirror = */ true)] })); + } + renderFgSegs(headerHeight, segs, segTops, todayRange, isMirror) { + const { props, segHeightRefMap } = this; + const { colWidth, eventSelection, cellIsMicro } = props; + const colCount = props.cells.length; + const defaultDisplayEventEnd = props.cells.length === 1; + const nodes = []; + for (const seg of segs) { + const key = getEventPartKey(seg); + const { standinFor, eventRange } = seg; + const { instanceId } = eventRange.instance; + if (standinFor) { + continue; + } + const { insetInlineStart, insetInlineEnd } = computeHorizontalsFromSeg(seg, colWidth, colCount); + const localTop = segTops.get(standinFor ? getEventPartKey(standinFor) : key) ?? (isMirror ? 0 : undefined); + const top = headerHeight != null && localTop != null + ? headerHeight + localTop + : undefined; + const isDragging = Boolean(props.eventDrag && props.eventDrag.affectedInstances[instanceId]); + const isResizing = Boolean(props.eventResize && props.eventResize.affectedInstances[instanceId]); + const isInvisible = !isMirror && (isDragging || isResizing || standinFor || top == null); + const isListItem = hasListItemDisplay(seg); + const isSelected = instanceId === eventSelection; + nodes.push(u$1(DayGridEventHarness, { className: seg.start ? classNames.fakeBorderS : '', style: { + visibility: isInvisible ? 'hidden' : undefined, + top, + insetInlineStart, + insetInlineEnd, + zIndex: isSelected ? 1000 : 0, // container inner z-indexes; HACK: relies on hardcoded z-index offset; fragile if stacking context changes + }, heightRef: (!standinFor && !isMirror) + ? segHeightRefMap.createRef(key) + : null, children: u$1(StandardEvent, { display: isListItem ? 'list-item' : 'row', eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isMirror: isMirror, isSelected: isSelected, isNarrow: props.cellIsNarrow, defaultTimeFormat: DEFAULT_TABLE_EVENT_TIME_FORMAT, defaultDisplayEventEnd: defaultDisplayEventEnd, disableResizing: isListItem, forcedTimeText: cellIsMicro ? '' : undefined, ...getEventRangeMeta(eventRange, todayRange) }) }, key)); + } + return nodes; + } + renderFillSegs(segs, fillType) { + const { props, context } = this; + const { todayRange, colWidth } = props; + const colCount = props.cells.length; + const nodes = []; + for (const seg of segs) { + const key = seg.start + ':' + seg.end; // NOTE: don't use date, because could be multiple of same (w/ resources) + const { insetInlineStart, insetInlineEnd } = computeHorizontalsFromSeg(seg, colWidth, colCount); + const isVisible = !seg.standinFor; + nodes.push(u$1("div", { className: classNames.fillY, style: { + visibility: (isVisible ? '' : 'hidden'), + insetInlineStart, + insetInlineEnd, + }, children: fillType === 'bg-event' ? + u$1(BgEvent, { eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isNarrow: props.cellIsNarrow, isVertical: false, ...getEventRangeMeta(seg.eventRange, todayRange) }) : (renderFill(fillType, context.options)) }, key)); + } + return u$1(S, { children: nodes }); + } + // Sizing + // ----------------------------------------------------------------------------------------------- + componentDidMount() { + this._isUnmounting = false; + const { rootEl } = this; // TODO: make dynamic with useEffect + this.disconnectHeight = watchHeight(rootEl, (contentHeight) => { + setRef(this.props.heightRef, contentHeight); + }); + } + componentWillUnmount() { + this._isUnmounting = true; + this.disconnectHeight(); + setRef(this.props.heightRef, null); + } + computeFgDims() { + const { cells } = this.props; + const headerHeightMap = this.headerHeightRefMap.current; + const mainHeightMap = this.mainHeightRefMap.current; + let maxMainTop; + let minMainBottom; + for (const cell of cells) { + const mainTop = headerHeightMap.get(cell.key); + const mainHeight = mainHeightMap.get(cell.key); + if (mainTop != null) { + if (maxMainTop === undefined || mainTop > maxMainTop) { + maxMainTop = mainTop; + } + if (mainHeight != null) { + const mainBottom = mainTop + mainHeight; + if (minMainBottom === undefined || mainBottom < minMainBottom) { + minMainBottom = mainBottom; + } + } + } + } + return [ + maxMainTop, + minMainBottom != null && maxMainTop != null + ? minMainBottom - maxMainTop + : undefined, + ]; + } + // Internal Utils + // ----------------------------------------------------------------------------------------------- + getMirrorSegs() { + let { props } = this; + if (props.eventResize && props.eventResize.segs.length) { // messy check + return props.eventResize.segs; + } + return []; + } + getHighlightSegs() { + let { props } = this; + if (props.eventDrag && props.eventDrag.segs.length) { // messy check + return props.eventDrag.segs; + } + if (props.eventResize && props.eventResize.segs.length) { // messy check + return props.eventResize.segs; + } + return props.dateSelectionSegs; + } + } + // Utils + // ------------------------------------------------------------------------------------------------- + function buildWeekNumberRenderProps(weekDateMarker, context, isNarrow, hasNavLink) { + const { dateEnv, options } = context; + const weekNum = dateEnv.computeWeekNumber(weekDateMarker); + const weekNumTextParts = dateEnv.formatToParts(weekDateMarker, options.weekNumberFormat || DEFAULT_WEEK_NUM_FORMAT$1); + const weekNumText = joinDateTimeFormatParts(weekNumTextParts); + const weekDateZoned = dateEnv.toDate(weekDateMarker); + return { + num: weekNum, + text: weekNumText, + textParts: weekNumTextParts, + date: weekDateZoned, + isNarrow, + hasNavLink, + }; + } + + class DayGridRows extends DateComponent { + constructor() { + super(...arguments); + // memo + this.splitBusinessHourSegs = memoize(splitSegsByRow); + this.splitBgEventSegs = memoize(splitAllDaySegsByRow); + this.splitFgEventSegs = memoize(splitSegsByRow); + this.splitDateSelectionSegs = memoize(splitSegsByRow); + this.splitEventDrag = memoize(splitInteractionByRow); + this.splitEventResize = memoize(splitInteractionByRow); + // internal + this.rowHeightRefMap = new RefMap((height, key) => { + // HACKy way of syncing RefMap results with prop + const { rowHeightRefMap } = this.props; + if (rowHeightRefMap) { + rowHeightRefMap.handleValue(height, key); + } + }); + this.handleRootEl = (rootEl) => { + this.rootEl = rootEl; + if (rootEl) { + this.context.registerInteractiveComponent(this, { + el: rootEl, + isHitComboAllowed: this.props.isHitComboAllowed, + }); + } + else { + this.context.unregisterInteractiveComponent(this); + } + }; + } + render() { + let { props, context, rowHeightRefMap } = this; + let { options } = context; + let { cellRows } = props; + let rowCount = cellRows.length; + // Will cause rows to not be reused across months + let firstCellKey = cellRows[0]?.[0]?.key || ''; + let fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, rowCount); + let bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, rowCount); + let businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, rowCount); + let dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, rowCount); + let eventDragByRow = this.splitEventDrag(props.eventDrag, rowCount); + let eventResizeByRow = this.splitEventResize(props.eventResize, rowCount); + let isHeightAuto = getIsHeightAuto(options); + let rowHeightsRedistribute = !props.forPrint && !isHeightAuto; + let rowBasis = computeRowBasis(props.visibleWidth, rowCount, isHeightAuto, options); + return (u$1("div", { role: 'rowgroup', className: joinClassNames(props.className, + // HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard: + // https://stackoverflow.com/a/60256345 + !props.forPrint && classNames.flexCol), style: { width: props.width }, ref: this.handleRootEl, children: cellRows.map((cells, row) => (u$1(DayGridRow, { role: 'row', dateProfile: props.dateProfile, todayRange: props.todayRange, cells: cells, cellIsNarrow: props.cellIsNarrow, cellIsMicro: props.cellIsMicro, showDayNumbers: rowCount > 1, showWeekNumbers: rowCount > 1 && options.weekNumbers, forPrint: props.forPrint, + // if not auto-height, distribute height of container somewhat evently to rows + className: joinClassNames(rowHeightsRedistribute && classNames.grow, rowCount > 1 && classNames.breakInsideAvoid, // don't avoid breaks for single tall row + row < rowCount - 1 ? classNames.borderOnlyB : classNames.borderNone), + // content + fgEventSegs: fgEventSegsByRow[row], bgEventSegs: bgEventSegsByRow[row], businessHourSegs: businessHourSegsByRow[row], dateSelectionSegs: dateSelectionSegsByRow[row], eventSelection: props.eventSelection, eventDrag: eventDragByRow[row], eventResize: eventResizeByRow[row], dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows, + // dimensions + colWidth: props.colWidth, basis: rowBasis, + // refs + heightRef: rowHeightRefMap.createRef(cells[0].key) }, firstCellKey + ':' + cells[0].key))) })); + } + // Hit System + // ----------------------------------------------------------------------------------------------- + queryHit(isRtl, positionLeft, positionTop, elWidth) { + const { props } = this; + const colCount = props.cellRows[0].length; + const { col, left, right } = computeColFromPosition(positionLeft, elWidth, props.colWidth, colCount, isRtl); + const { row, top, bottom } = computeRowFromPosition(positionTop, props.cellRows, this.rowHeightRefMap.current); + const cell = props.cellRows[row][col]; + const cellStartDate = cell.date; + const cellEndDate = addDays(cellStartDate, 1); + return { + dateProfile: props.dateProfile, + dateSpan: { + range: { + start: cellStartDate, + end: cellEndDate, + }, + allDay: true, + ...cell.dateSpanProps, + }, + getDayEl: () => getCellEl(getRowEl(this.rootEl, row), col), + rect: { + left, + right, + top, + bottom, + }, + layer: 0, + }; + } + } + // Utils + // ------------------------------------------------------------------------------------------------- + function isSegAllDay(seg) { + return seg.eventRange.def.allDay; + } + function splitAllDaySegsByRow(segs, rowCnt) { + return splitSegsByRow(segs.filter(isSegAllDay), rowCnt); + } + /* + Amount of height a row should consume prior to expanding + We don't want to use min-height with flexbox because we leverage min-height:auto, + which yields value based on natural height of events + */ + function computeRowBasis(visibleWidth, // should INCLUDE any scrollbar width to avoid oscillation + rowCount, isHeightAuto, options) { + if (visibleWidth != null) { + // ensure a consistent row min-height modelled after a month with 6 rows respecting aspectRatio + // will result in same minHeight regardless of weekends, dayMinWidth, height:auto + const rowBasis = visibleWidth / options.aspectRatio / 6; + // don't give minHeight when single-month non-auto-height + // TODO: better way to detect this with DateProfile? + return (rowCount > 6 || isHeightAuto) ? rowBasis : 0; + } + return 0; + } + + class DayGridHeaderCell extends BaseComponent { + constructor() { + super(...arguments); + this.state = {}; + // memo + this.buildDayHeaderText = memoize(buildDayHeaderText); + this.handleInnerEl = (innerEl) => { + if (this.disconnectSize) { + this.disconnectSize(); + this.disconnectSize = undefined; + } + if (innerEl) { + this.disconnectSize = watchSize(innerEl, (width, height) => { + if (this._isUnmounting) + return; + setRef(this.props.innerHeightRef, height); + this.setState({ innerWidth: width }); + }); + } + else { + setRef(this.props.innerHeightRef, null); + } + }; + } + render() { + const { props, state, context } = this; + const { renderConfig, dataConfig } = props; + const totalColWidth = props.colWidth != null + ? props.colWidth * (dataConfig.colSpan || 1) + : undefined; + // HACK + const isDisabled = dataConfig.renderProps.isDisabled; + const finalRenderProps = renderConfig.dayHeaderFormat + ? this.buildDayHeaderRenderProps(dataConfig.renderProps, props.cellIsNarrow, props.rowLevel, props.cellIsMicro, dataConfig.dateMarker, renderConfig.dayHeaderFormat, Boolean(renderConfig.datesRepDistinctDays), context.dateEnv) + : { + ...dataConfig.renderProps, + isNarrow: props.cellIsNarrow, + level: props.rowLevel, + }; + /* + TODO: DRY with TimelineHeaderCell + */ + const alignInput = renderConfig.align; + const align = // normalized string-enum value + typeof alignInput === 'function' + ? alignInput({ level: props.rowLevel, inPopover: dataConfig.renderProps.inPopover, isNarrow: props.cellIsNarrow }) + : alignInput; + const stickyInput = renderConfig.sticky; + const isSticky = props.rowLevel > 0 && + stickyInput !== false && ( + // if center-aligned, and wants to be sticky, must be >75% viewport width, + // to avoid looking awkwardly aligned + align !== 'center' || (totalColWidth != null && + props.viewportWidth != null && + totalColWidth > props.viewportWidth * 0.75)); + let edgeCoord; + if (isSticky) { + if (align === 'center') { + if (state.innerWidth != null) { + edgeCoord = `calc(50% - ${state.innerWidth / 2}px)`; + } + } + else { + edgeCoord = (typeof stickyInput === 'number' || + typeof stickyInput === 'string') ? stickyInput : 0; + } + } + return (u$1(ContentContainer, { tag: 'div', attrs: { + role: 'columnheader', + 'aria-colspan': dataConfig.colSpan, + ...dataConfig.attrs, + }, className: joinClassNames(dataConfig.className, classNames.noMargin, classNames.noPadding, classNames.flexCol, props.borderStart ? classNames.borderOnlyS : classNames.borderNone, align === 'center' ? classNames.alignCenter : + align === 'end' ? classNames.alignEnd : + classNames.alignStart, props.colWidth == null && classNames.liquid, !isSticky && classNames.crop), style: { + width: totalColWidth, + }, renderProps: finalRenderProps, generatorName: renderConfig.generatorName, customGenerator: renderConfig.customGenerator, defaultGenerator: renderText$1, classNameGenerator: + // don't use custom classNames if disabled + // TODO: make DRY with DayCellContainer + isDisabled ? undefined : renderConfig.classNameGenerator, didMount: renderConfig.didMount, willUnmount: renderConfig.willUnmount, children: (InnerContainer) => (u$1("div", { ref: this.handleInnerEl, className: joinClassNames(classNames.flexCol, classNames.noShrink, classNames.whiteSpaceNoWrap, isSticky && classNames.sticky), style: { + left: edgeCoord, + right: edgeCoord, + }, children: u$1(InnerContainer, { tag: 'div', attrs: dataConfig.innerAttrs, className: generateClassName(renderConfig.innerClassNameGenerator, finalRenderProps) }) })) })); + } + componentDidMount() { + this._isUnmounting = false; + } + componentWillUnmount() { + this._isUnmounting = true; + } + buildDayHeaderRenderProps(renderProps, cellIsNarrow, rowLevel, cellIsMicro, dateMarker, dayHeaderFormat, datesRepDistinctDays, dateEnv) { + const baseText = this.buildDayHeaderText(datesRepDistinctDays ? dateMarker : renderProps.date, dayHeaderFormat, datesRepDistinctDays, dateEnv); + const textData = cellIsMicro + ? this.buildDayHeaderText(dateMarker, dayHeaderMicroFormat, false, dateEnv) + : baseText; + return { + ...renderProps, + isNarrow: cellIsNarrow, + level: rowLevel, + text: textData.text, + textParts: textData.textParts, + weekdayText: cellIsMicro ? textData.text : baseText.weekdayText, + dayNumberText: baseText.dayNumberText, + }; + } + } + function buildDayHeaderText(date, formatter, includeDayNumber, dateEnv) { + const textParts = dateEnv.formatToParts(date, formatter); + return { + text: joinDateTimeFormatParts(textParts), + textParts, + weekdayText: findWeekdayText(textParts), + dayNumberText: includeDayNumber ? findDayNumberText(textParts) : '', + }; + } + + class DayGridHeaderRow extends BaseComponent { + constructor() { + super(...arguments); + // ref + this.innerHeightRefMap = new RefMap(() => { + afterSize(this.handleInnerHeights); + }); + this.handleInnerHeights = () => { + if (this._isUnmounting) + return; + const innerHeightMap = this.innerHeightRefMap.current; + let max = 0; + for (const innerHeight of innerHeightMap.values()) { + max = Math.max(max, innerHeight); + } + if (this.currentInnerHeight !== max) { + this.currentInnerHeight = max; + setRef(this.props.innerHeightRef, max); + } + }; + } + render() { + const { props, context } = this; + const { options } = context; + return (u$1("div", { role: props.role /* !!! */, "aria-rowindex": props.rowIndex != null ? 1 + props.rowIndex : undefined, className: joinClassNames(options.dayHeaderRowClass, props.className, classNames.flexRow, classNames.contentBox, props.borderBottom ? classNames.borderOnlyB : classNames.borderNone), style: { + height: props.height, + }, children: props.dataConfigs.map((dataConfig, cellI) => (u$1(DayGridHeaderCell, { renderConfig: props.renderConfig, dataConfig: dataConfig, borderStart: Boolean(cellI), colWidth: props.colWidth, viewportWidth: props.viewportWidth, innerHeightRef: this.innerHeightRefMap.createRef(dataConfig.key), cellIsNarrow: props.cellIsNarrow, cellIsMicro: props.cellIsMicro, rowLevel: props.rowLevel }, dataConfig.key))) })); + } + componentDidMount() { + this._isUnmounting = false; + } + componentWillUnmount() { + this._isUnmounting = true; + this.currentInnerHeight = undefined; + setRef(this.props.innerHeightRef, null); + } + } + + /* + TODO: kill this class in favor of DayGridHeaderRows? + */ + class DayGridHeader extends BaseComponent { + render() { + const { props } = this; + const { headerTiers } = props; + return (u$1("div", { role: 'rowgroup', className: joinClassNames(props.className, classNames.flexCol, props.width == null && classNames.liquid), style: { + width: props.width, + }, children: headerTiers.map((rowConfig, i) => (k$1(DayGridHeaderRow, { ...rowConfig, key: i, role: 'row', borderBottom: i < headerTiers.length - 1, colWidth: props.colWidth, viewportWidth: props.viewportWidth, cellIsNarrow: props.cellIsNarrow, cellIsMicro: props.cellIsMicro, rowLevel: headerTiers.length - i - 1 }))) })); + } + } + + class DayGridLayoutNormal extends BaseComponent { + constructor() { + super(...arguments); + this.state = {}; + this.handleScroller = (scroller) => { + setRef(this.props.scrollerRef, scroller); + }; + this.handleTotalWidth = (totalWidth) => { + if (this._isUnmounting) + return; + this.setState({ totalWidth }); + }; + this.handleClientWidth = (clientWidth) => { + if (this._isUnmounting) + return; + this.setState({ clientWidth }); + }; + } + render() { + const { props, state, context } = this; + const { options } = context; + const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options); + const { totalWidth, clientWidth } = state; + let endScrollbarWidth = (totalWidth != null && clientWidth != null) + ? totalWidth - clientWidth + : undefined; + // HACK when clientWidth does NOT include body-border, compared to totalWidth + if (endScrollbarWidth < 3) { + endScrollbarWidth = 0; + } + const verticalScrollbars = !props.forPrint && !getIsHeightAuto(options); + const tableHeaderSticky = !props.forPrint && getTableHeaderSticky(options); + const colCount = props.cellRows[0].length; + const cellWidth = clientWidth != null ? clientWidth / colCount : undefined; + const cellIsMicro = cellWidth != null && cellWidth <= dayMicroWidth; + const cellIsNarrow = cellIsMicro || (cellWidth != null && cellWidth <= options.dayNarrowWidth); + return (u$1(S, { children: [options.dayHeaders && (u$1("div", { className: joinClassNames(generateClassName(options.tableHeaderClass, { + isSticky: tableHeaderSticky, + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: 0, + }), classNames.printHeader, // either flexCol or table-header-group + tableHeaderSticky && classNames.tableHeaderSticky), children: [u$1("div", { className: classNames.flexRow, children: [u$1(DayGridHeader, { headerTiers: props.headerTiers, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro }), Boolean(endScrollbarWidth) && (u$1("div", { className: joinClassNames(generateClassName(options.fillerClass, { inTableHeader: true }), classNames.borderOnlyS), style: { minWidth: endScrollbarWidth } }))] }), u$1("div", { className: generateClassName(options.dayHeaderDividerClass, { + isSticky: tableHeaderSticky, + multiMonthColumns: 0, + options: { allDaySlot: Boolean(options.allDaySlot) }, + }) })] })), u$1(Scroller, { vertical: verticalScrollbars, className: joinClassNames(generateClassName(options.tableBodyClass, { + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: 0, + }), + // HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard: + // https://stackoverflow.com/a/60256345 + !props.forPrint && classNames.flexCol, verticalScrollbars && classNames.liquid), ref: this.handleScroller, clientWidthRef: this.handleClientWidth, children: u$1(DayGridRows, { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: props.cellRows, forPrint: props.forPrint, isHitComboAllowed: props.isHitComboAllowed, className: classNames.grow, dayMaxEvents: props.forPrint ? undefined : options.dayMaxEvents, dayMaxEventRows: options.dayMaxEventRows, + // content + fgEventSegs: props.fgEventSegs, bgEventSegs: props.bgEventSegs, businessHourSegs: props.businessHourSegs, dateSelectionSegs: props.dateSelectionSegs, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection, + // dimensions + visibleWidth: totalWidth, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro, + // refs + rowHeightRefMap: props.rowHeightRefMap }) }), u$1(Ruler, { widthRef: this.handleTotalWidth })] })); + } + componentDidMount() { + this._isUnmounting = false; + } + componentWillUnmount() { + this._isUnmounting = true; + } + } + + class FooterScrollbar extends BaseComponent { + constructor() { + super(...arguments); + this.rootElRef = M$1(); + } + render() { + const { props } = this; + // NOTE: we need a wrapper around the Scroller because if scrollbars appear/hide, + // the outer dimensions change, but the inner dimensions do not. The Scroller's + // dimension-watching, when used in ponyfill-mode, can't fire on border-box change, so we + // workaround it by monitoring dimensions of a wrapper instead + return (u$1("div", { ref: this.rootElRef, className: joinClassNames(classNames.footerScrollbar, props.isSticky && classNames.footerScrollbarSticky), children: u$1(Scroller, { horizontal: true, ref: props.scrollerRef, children: u$1("div", { style: { minWidth: props.canvasWidth } }) }) })); + } + componentDidMount() { + this._isUnmounting = false; + this.disconnectHeight = watchHeight(this.rootElRef.current, (height) => { + if (this._isUnmounting) + return; + setRef(this.props.scrollbarWidthRef, height); + }); + } + componentWillUnmount() { + this._isUnmounting = true; + this.disconnectHeight(); + setRef(this.props.scrollbarWidthRef, null); + } + } + + class DayGridLayoutPannable extends BaseComponent { + constructor() { + super(...arguments); + this.state = {}; + this.headerScrollerRef = M$1(); + this.bodyScrollerRef = M$1(); + this.footerScrollerRef = M$1(); + // Sizing + // ----------------------------------------------------------------------------------------------- + this.handleTotalWidth = (totalWidth) => { + if (this._isUnmounting) + return; + this.setState({ totalWidth }); + }; + this.handleClientWidth = (clientWidth) => { + if (this._isUnmounting) + return; + this.setState({ clientWidth }); + }; + } + render() { + const { props, state, context } = this; + const { options } = context; + const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options); + const { totalWidth, clientWidth } = state; + const endScrollbarWidth = (totalWidth != null && clientWidth != null) + ? totalWidth - clientWidth + : undefined; + const verticalScrollbars = !props.forPrint && !getIsHeightAuto(options); + const tableHeaderSticky = !props.forPrint && getTableHeaderSticky(options); + const footerScrollbarSticky = !props.forPrint && getFooterScrollbarSticky(options); + const colCount = props.cellRows[0].length; + const [canvasWidth, colWidth] = computeColWidth(colCount, props.dayMinWidth, clientWidth); + const cellIsMicro = colWidth != null && colWidth <= dayMicroWidth; + const cellIsNarrow = cellIsMicro || (colWidth != null && colWidth <= options.dayNarrowWidth); + return (u$1(S, { children: [options.dayHeaders && (u$1("div", { className: joinClassNames(generateClassName(options.tableHeaderClass, { + isSticky: tableHeaderSticky, + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: 0, + }), classNames.printHeader, // either flexCol or table-header-group + tableHeaderSticky && classNames.tableHeaderSticky), children: [u$1(Scroller, { horizontal: true, hideScrollbars: true, className: classNames.flexRow, ref: this.headerScrollerRef, children: [u$1(DayGridHeader, { headerTiers: props.headerTiers, colWidth: colWidth, viewportWidth: clientWidth, width: canvasWidth, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro }), Boolean(endScrollbarWidth) && (u$1("div", { className: joinClassNames(generateClassName(options.fillerClass, { inTableHeader: true }), classNames.borderOnlyS), style: { minWidth: endScrollbarWidth } }))] }), u$1("div", { className: generateClassName(options.dayHeaderDividerClass, { + isSticky: tableHeaderSticky, + multiMonthColumns: 0, + options: { allDaySlot: Boolean(options.allDaySlot) }, + }) })] })), u$1(Scroller, { vertical: verticalScrollbars, horizontal: true, hideScrollbars: footerScrollbarSticky || + props.forPrint // prevents blank space in print-view on Safari + , className: joinClassNames(generateClassName(options.tableBodyClass, { + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: 0, + }), + // HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard: + // https://stackoverflow.com/a/60256345 + !props.forPrint && classNames.flexCol, verticalScrollbars && classNames.liquid), ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth, children: u$1(DayGridRows, { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: props.cellRows, forPrint: props.forPrint, isHitComboAllowed: props.isHitComboAllowed, className: classNames.grow, dayMaxEvents: props.forPrint ? undefined : options.dayMaxEvents, dayMaxEventRows: options.dayMaxEventRows, + // content + fgEventSegs: props.fgEventSegs, bgEventSegs: props.bgEventSegs, businessHourSegs: props.businessHourSegs, dateSelectionSegs: props.dateSelectionSegs, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection, + // dimensions + colWidth: colWidth, width: canvasWidth, visibleWidth: totalWidth, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro, + // refs + rowHeightRefMap: props.rowHeightRefMap }) }), Boolean(footerScrollbarSticky) && (u$1(FooterScrollbar, { isSticky: true, canvasWidth: canvasWidth, scrollerRef: this.footerScrollerRef })), u$1(Ruler, { widthRef: this.handleTotalWidth })] })); + } + // Lifecycle + // ----------------------------------------------------------------------------------------------- + componentDidMount() { + this._isUnmounting = false; + // scroller + const ScrollerSyncer = getScrollerSyncerClass(this.context.pluginHooks); + this.syncedScroller = new ScrollerSyncer(true); // horizontal=true + setRef(this.props.scrollerRef, this.syncedScroller); + this.updateSyncedScroller(); + } + componentDidUpdate() { + // scroller + this.updateSyncedScroller(); + } + componentWillUnmount() { + this._isUnmounting = true; + // scroller + this.syncedScroller.destroy(); + } + // Scrolling + // ----------------------------------------------------------------------------------------------- + updateSyncedScroller() { + this.syncedScroller.handleChildren([ + this.headerScrollerRef.current, + this.bodyScrollerRef.current, + this.footerScrollerRef.current, + ]); + } + } + + class DayGridLayout extends BaseComponent { + constructor() { + super(...arguments); + // ref + this.scrollerRef = M$1(); + this.rowHeightRefMap = new RefMap(() => { + afterSize(this.updateScrollY); + }); + this.scrollDate = null; + this.updateScrollY = () => { + if (this._isUnmounting) + return; + const rowHeightMap = this.rowHeightRefMap.current; + const scroller = this.scrollerRef.current; + // Since updateScrollY is called by rowHeightRefMap, could be called with null during cleanup, + // and the scroller might not exist + if (scroller && this.scrollDate) { + let scrollTop = computeTopFromDate(this.scrollDate, this.props.cellRows, rowHeightMap); + if (scrollTop != null) { + if (scrollTop) { + scrollTop++; // clear top border + } + scroller.scrollTo({ y: scrollTop }); + } + } + }; + this.handleScrollEnd = (isDevice) => { + if (isDevice) { + this.scrollDate = null; + } + }; + } + render() { + const { props, context } = this; + const { options } = context; + const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options); + const businessHourSegs = props.forPrint ? [] : props.businessHourSegs; + const dateSelectionSegs = props.forPrint ? [] : props.dateSelectionSegs; + const eventDrag = props.forPrint ? null : props.eventDrag; + const eventResize = props.forPrint ? null : props.eventResize; + const commonLayoutProps = { + ...props, + businessHourSegs, + dateSelectionSegs, + eventDrag, + eventResize, + scrollerRef: this.scrollerRef, + rowHeightRefMap: this.rowHeightRefMap, + }; + return (u$1(ViewContainer, { viewSpec: context.viewSpec, attrs: { + role: 'grid', + 'aria-rowcount': props.headerTiers.length + props.cellRows.length, + 'aria-colcount': props.cellRows[0].length, + 'aria-labelledby': props.labelId, + 'aria-label': props.labelStr, + }, className: joinClassNames(props.className, classNames.printRoot, // either flexCol or table + generateClassName(options.tableClass, { + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: 0, + })), children: options.dayMinWidth ? (u$1(DayGridLayoutPannable, { ...commonLayoutProps, dayMinWidth: options.dayMinWidth })) : (u$1(DayGridLayoutNormal, { ...commonLayoutProps })) })); + } + // Lifecycle + // ----------------------------------------------------------------------------------------------- + componentDidMount() { + this._isUnmounting = false; + this.resetScroll(); + this.scrollerRef.current.addScrollEndListener(this.handleScrollEnd); + } + componentDidUpdate(prevProps) { + if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) { + this.resetScroll(); + } + } + componentWillUnmount() { + this._isUnmounting = true; + this.scrollerRef.current.removeScrollEndListener(this.handleScrollEnd); + } + // Scrolling + // ----------------------------------------------------------------------------------------------- + resetScroll() { + this.scrollDate = this.props.dateProfile.currentDate; + this.updateScrollY(); + const scroller = this.scrollerRef.current; + scroller.scrollTo({ x: 0 }); + } + } + + const EMPTY_EVENT_STORE = createEmptyEventStore(); // for purecomponents. TODO: keep elsewhere + class Splitter { + constructor() { + this.getKeysForEventDefs = memoize(this._getKeysForEventDefs); + this.splitDateSelection = memoize(this._splitDateSpan); + this.splitEventStore = memoize(this._splitEventStore); + this.splitIndividualUi = memoize(this._splitIndividualUi); + this.splitEventDrag = memoize(this._splitInteraction); + this.splitEventResize = memoize(this._splitInteraction); + this.eventUiBuilders = {}; // TODO: typescript protection + } + splitProps(props) { + let keyInfos = this.getKeyInfo(props); + let defKeys = this.getKeysForEventDefs(props.eventStore); + let dateSelections = this.splitDateSelection(props.dateSelection); + let individualUi = this.splitIndividualUi(props.eventUiBases, defKeys); // the individual *bases* + let eventStores = this.splitEventStore(props.eventStore, defKeys); + let eventDrags = this.splitEventDrag(props.eventDrag); + let eventResizes = this.splitEventResize(props.eventResize); + let splitProps = {}; + this.eventUiBuilders = mapHash(keyInfos, (info, key) => this.eventUiBuilders[key] || memoize(buildEventUiForKey)); + for (let key in keyInfos) { + let keyInfo = keyInfos[key]; + let eventStore = eventStores[key] || EMPTY_EVENT_STORE; + let buildEventUi = this.eventUiBuilders[key]; + splitProps[key] = { + businessHours: keyInfo.businessHours || props.businessHours, + dateSelection: dateSelections[key] || null, + eventStore, + eventUiBases: buildEventUi(props.eventUiBases[''], keyInfo.ui, individualUi[key]), + eventDrag: eventDrags[key] || null, + eventResize: eventResizes[key] || null, + eventSelection: eventStore.instances[props.eventSelection] ? props.eventSelection : '', + }; + } + return splitProps; + } + _splitDateSpan(dateSpan) { + let dateSpans = {}; + if (dateSpan) { + let keys = this.getKeysForDateSpan(dateSpan); + for (let key of keys) { + dateSpans[key] = dateSpan; + } + } + return dateSpans; + } + _getKeysForEventDefs(eventStore) { + return mapHash(eventStore.defs, (eventDef) => this.getKeysForEventDef(eventDef)); + } + _splitEventStore(eventStore, defKeys) { + let { defs, instances } = eventStore; + let splitStores = {}; + for (let defId in defs) { + for (let key of defKeys[defId]) { + if (!splitStores[key]) { + splitStores[key] = createEmptyEventStore(); + } + splitStores[key].defs[defId] = defs[defId]; + } + } + for (let instanceId in instances) { + let instance = instances[instanceId]; + for (let key of defKeys[instance.defId]) { + if (splitStores[key]) { // must have already been created + splitStores[key].instances[instanceId] = instance; + } + } + } + return splitStores; + } + _splitIndividualUi(eventUiBases, defKeys) { + let splitHashes = {}; + for (let defId in eventUiBases) { + if (defId) { // not the '' key + for (let key of defKeys[defId]) { + if (!splitHashes[key]) { + splitHashes[key] = {}; + } + splitHashes[key][defId] = eventUiBases[defId]; + } + } + } + return splitHashes; + } + _splitInteraction(interaction) { + let splitStates = {}; + if (interaction) { + let affectedStores = this._splitEventStore(interaction.affectedEvents, this._getKeysForEventDefs(interaction.affectedEvents)); + // can't rely on defKeys because event data is mutated + let mutatedKeysByDefId = this._getKeysForEventDefs(interaction.mutatedEvents); + let mutatedStores = this._splitEventStore(interaction.mutatedEvents, mutatedKeysByDefId); + let populate = (key) => { + if (!splitStates[key]) { + splitStates[key] = { + affectedEvents: affectedStores[key] || EMPTY_EVENT_STORE, + mutatedEvents: mutatedStores[key] || EMPTY_EVENT_STORE, + isEvent: interaction.isEvent, + }; + } + }; + for (let key in affectedStores) { + populate(key); + } + for (let key in mutatedStores) { + populate(key); + } + } + return splitStates; + } + } + function buildEventUiForKey(allUi, eventUiForKey, individualUi) { + let baseParts = []; + if (allUi) { + baseParts.push(allUi); + } + if (eventUiForKey) { + baseParts.push(eventUiForKey); + } + let stuff = { + '': combineEventUis(baseParts), + }; + if (individualUi) { + Object.assign(stuff, individualUi); + } + return stuff; + } + + class AllDaySplitter extends Splitter { + getKeyInfo() { + return { + allDay: {}, + timed: {}, + }; + } + getKeysForDateSpan(dateSpan) { + if (dateSpan.allDay) { + return ['allDay']; + } + return ['timed']; + } + getKeysForEventDef(eventDef) { + if (!eventDef.allDay) { + return ['timed']; + } + if (hasBgRendering(eventDef)) { + return ['timed', 'allDay']; + } + return ['allDay']; + } + } + + class DayTimeColsSlicer extends Slicer { + sliceRange(range, dayRanges) { + let segs = []; + for (let col = 0; col < dayRanges.length; col += 1) { + let segRange = intersectRanges(range, dayRanges[col]); + if (segRange) { + segs.push({ + startDate: segRange.start, + endDate: segRange.end, + isStart: segRange.start.valueOf() === range.start.valueOf(), + isEnd: segRange.end.valueOf() === range.end.valueOf(), + col, + }); + } + } + return segs; + } + } + + /* + TODO: more DRY with daygrid? + can be given null/undefined! + */ + function organizeSegsByCol(segs, colCount) { + let segsByCol = []; + let i; + for (i = 0; i < colCount; i += 1) { + segsByCol.push([]); + } + if (segs) { + for (i = 0; i < segs.length; i += 1) { + segsByCol[segs[i].col].push(segs[i]); + } + } + return segsByCol; + } + /* + TODO: more DRY with daygrid? + can be given null/undefined! + */ + function splitInteractionByCol(ui, colCount) { + let byRow = []; + if (!ui) { + for (let i = 0; i < colCount; i += 1) { + byRow[i] = null; + } + } + else { + for (let i = 0; i < colCount; i += 1) { + byRow[i] = { + affectedInstances: ui.affectedInstances, + isEvent: ui.isEvent, + segs: [], + }; + } + for (let seg of ui.segs) { + byRow[seg.col].segs.push(seg); + } + } + return byRow; + } + + // potential nice values for the slot-duration and interval-duration + // from largest to smallest + const STOCK_SUB_DURATIONS = [ + { hours: 1 }, + { minutes: 30 }, + { minutes: 15 }, + { seconds: 30 }, + { seconds: 15 }, + ]; + function buildSlatMetas(slotMinTime, slotMaxTime, explicitLabelInterval, slotDuration, dateEnv) { + let dayStart = new Date(0); + let slatTime = slotMinTime; + let slatIterator = createDuration(0); + let labelInterval = explicitLabelInterval || computeLabelInterval(slotDuration); + let metas = []; + let i = 0; + while (asRoughMs(slatTime) < asRoughMs(slotMaxTime)) { + let date = dateEnv.add(dayStart, slatTime); + let isLabeled = wholeDivideDurations(slatIterator, labelInterval) !== null; + metas.push({ + date, + time: slatTime, + key: date.toISOString(), // we can't use the isoTimeStr for uniqueness when minTime/maxTime beyone 0h/24h + isoTimeStr: formatIsoTimeString(date), + isLabeled, + isFirst: i === 0, + }); + slatTime = addDurations(slatTime, slotDuration); + slatIterator = addDurations(slatIterator, slotDuration); + i += 1; + } + return metas; + } + // Computes an automatic value for slotHeaderInterval + function computeLabelInterval(slotDuration) { + let i; + let labelInterval; + let slotsPerLabel; + // find the smallest stock label interval that results in more than one slots-per-label + for (i = STOCK_SUB_DURATIONS.length - 1; i >= 0; i -= 1) { + labelInterval = createDuration(STOCK_SUB_DURATIONS[i]); + slotsPerLabel = wholeDivideDurations(labelInterval, slotDuration); + if (slotsPerLabel !== null && slotsPerLabel > 1) { + return labelInterval; + } + } + return slotDuration; // fall back + } + + class TimeGridAllDayHeader extends BaseComponent { + constructor() { + super(...arguments); + // ref + this.innerElRef = M$1(); + } + render() { + let { props } = this; + let { options, viewApi } = this.context; + let renderProps = { + text: options.allDayText, + view: viewApi, + isNarrow: props.isNarrow, + }; + return (u$1(ContentContainer, { tag: "div", attrs: { + role: 'rowheader', + }, className: joinClassNames(classNames.flexRow, classNames.noMargin, classNames.noPadding, classNames.contentBox), style: { + width: props.width, + }, renderProps: renderProps, generatorName: "allDayHeaderContent", customGenerator: options.allDayHeaderContent, defaultGenerator: renderAllDayInner, classNameGenerator: options.allDayHeaderClass, didMount: options.allDayHeaderDidMount, willUnmount: options.allDayHeaderWillUnmount, children: (InnerContent) => (u$1("div", { className: joinClassNames(classNames.flexRow, classNames.noShrink, classNames.whiteSpacePre), ref: this.innerElRef, children: u$1(InnerContent, { tag: 'div', className: generateClassName(options.allDayHeaderInnerClass, renderProps) }) })) })); + } + componentDidMount() { + this._isUnmounting = false; + const { props } = this; + const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect + // TODO: only attach this if refs props present + this.disconnectInnerWidth = watchWidth(innerEl, (width) => { + if (this._isUnmounting) + return; + setRef(props.innerWidthRef, width); + }); + } + componentWillUnmount() { + this._isUnmounting = true; + this.disconnectInnerWidth(); + setRef(this.props.innerWidthRef, null); + } + } + function renderAllDayInner(renderProps) { + return renderProps.text; + } + + class TimeGridAllDayLane extends DateComponent { + constructor() { + super(...arguments); + this.heightRef = M$1(); + this.handleRootEl = (rootEl) => { + this.rootEl = rootEl; + if (rootEl) { + this.context.registerInteractiveComponent(this, { + el: rootEl, + }); + } + else { + this.context.unregisterInteractiveComponent(this); + } + }; + } + render() { + return (u$1(DayGridRow, { ...this.props, + /* BAD: these overwrite the props! caller might want to pass them */ + rootElRef: this.handleRootEl, heightRef: this.heightRef })); + } + queryHit(isRtl, positionLeft, positionTop, elWidth) { + const { props, heightRef } = this; + const colCount = props.cells.length; + const { col, left, right } = computeColFromPosition(positionLeft, elWidth, props.colWidth, colCount, isRtl); + const cell = props.cells[col]; + const cellStartDate = cell.date; + const cellEndDate = addDays(cellStartDate, 1); + return { + dateProfile: props.dateProfile, + dateSpan: { + range: { + start: cellStartDate, + end: cellEndDate, + }, + allDay: true, + ...cell.dateSpanProps, + }, + getDayEl: () => getCellEl(this.rootEl, col), + rect: { + left, + right, + top: 0, + bottom: heightRef.current, + }, + layer: 0, + }; + } + } + + function buildTimeColsModel(dateProfile, dateProfileGenerator, dateEnv) { + let daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); + return new DayTableModel(daySeries, false, dateEnv); + } + function buildDayRanges(dayTableModel, dateProfile, dateEnv) { + let ranges = []; + for (let date of dayTableModel.headerDates) { + ranges.push({ + start: dateEnv.add(date, dateProfile.slotMinTime), + end: dateEnv.add(date, dateProfile.slotMaxTime), + }); + } + return ranges; + } + function computeSlatHeight(expandRows, slatCnt, explicitSlatMinHeight = 0, slatInnerHeight, // from the "inner" i think + scrollerHeight) { + if (!slatInnerHeight || !scrollerHeight) { + return [undefined, false]; + } + const slatMinHeight = Math.max(slatInnerHeight + 1, explicitSlatMinHeight); + const slatLiquidHeight = scrollerHeight / slatCnt; + let slatLiquid; + let slatHeight; + if (expandRows && slatLiquidHeight >= slatMinHeight) { + slatLiquid = true; + slatHeight = slatLiquidHeight; + } + else { + slatLiquid = false; + slatHeight = slatMinHeight; + } + return [slatHeight, slatLiquid]; + } + /* + A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. + */ + function computeDateTopFrac(date, dateProfile, startOfDayDate) { + if (!startOfDayDate) { + startOfDayDate = startOfDay(date); + } + return computeTimeTopFrac(createDuration(date.valueOf() - startOfDayDate.valueOf()), dateProfile); + } + function computeTimeTopFrac(time, dateProfile) { + const startMs = asRoughMs(dateProfile.slotMinTime); + const endMs = asRoughMs(dateProfile.slotMaxTime); + let frac = (time.milliseconds - startMs) / (endMs - startMs); + frac = Math.max(0, frac); + frac = Math.min(1, frac); + return frac; + } + + function computeFgSegVerticals(segs, dateProfile, colDate, slatCnt, slatHeight, // in pixels + eventMinHeight, // in pixels + eventShortHeight) { + const res = []; + if (slatHeight != null) { + const totalHeight = slatHeight * slatCnt; + for (const seg of segs) { + const startFrac = computeDateTopFrac(seg.startDate, dateProfile, colDate); + const endFrac = computeDateTopFrac(seg.endDate, dateProfile, colDate); + const startCoord = startFrac * totalHeight; + let endCoord = endFrac * totalHeight; + let height = endCoord - startCoord; + if (eventMinHeight != null && height < eventMinHeight) { + height = eventMinHeight; + endCoord = startCoord + height; + } + res.push({ + start: startCoord, + end: endCoord, + size: height, + isShort: height <= eventShortHeight + }); + } + } + return res; + } + + /* + segs assumed sorted + */ + function buildWebPositioning(segs, segVerticals, strictOrder, maxDepth) { + const segRanges = []; + // isn't it true that there will either be ALL hcoords or NONE? can optimize + for (let i = 0; i < segs.length; i++) { + const segVertical = segVerticals[i]; + if (segVertical) { + segRanges.push({ + ...segs[i], + start: segVertical.start, + end: segVertical.end, + }); + } + } + const hierarchy = new SegHierarchy(segRanges, undefined, // 1 thickness for all segs + strictOrder, undefined, // maxCoord + maxDepth); + let web = buildWeb(hierarchy); + web = stretchWeb(web, 1); // all levelCoords/thickness will have 0.0-1.0 + const segRects = webToRects(web); + const hiddenGroups = groupIntersectingSegs(hierarchy.hiddenSegs); + return [segRects, hiddenGroups]; + } + /* + TODO: use SegHierarchy::traverseSegs for this? + */ + function buildWeb(hierarchy) { + const { placementsByLevel } = hierarchy; + const buildNode = cacheable((level, lateral) => level + ':' + lateral, (level, lateral) => { + let siblingRange = findNextLevelSegs(hierarchy, level, lateral); + let [nextLevelNodes, maxPressure] = buildNodes(siblingRange, buildNode); + let segPlacement = placementsByLevel[level][lateral]; + return [ + { ...segPlacement, nextLevelNodes }, + segPlacement.thickness + maxPressure, // the pressure builds + ]; + }); + const [topLevelNodes] = buildNodes(placementsByLevel.length + ? { level: 0, lateralStart: 0, lateralEnd: placementsByLevel[0].length } + : null, buildNode); + return topLevelNodes; + } + function buildNodes(siblingRange, buildNode) { + if (!siblingRange) { + return [[], 0]; + } + let { level, lateralStart, lateralEnd } = siblingRange; + let lateral = lateralStart; + let pairs = []; + while (lateral < lateralEnd) { + pairs.push(buildNode(level, lateral)); + lateral += 1; + } + pairs.sort(cmpDescPressures); + return [ + pairs.map(extractNode), // nodes + pairs[0][1], // first item's pressure + ]; + } + function cmpDescPressures(a, b) { + return b[1] - a[1]; + } + function extractNode(a) { + return a[0]; + } + function findNextLevelSegs(hierarchy, subjectLevel, subjectLateral) { + let { levelCoords, placementsByLevel } = hierarchy; + let subjectPlacement = placementsByLevel[subjectLevel][subjectLateral]; + let afterSubject = levelCoords[subjectLevel] + subjectPlacement.thickness; + let levelCnt = levelCoords.length; + let level = subjectLevel; + // skip past levels that are too high up + for (; level < levelCnt && levelCoords[level] < afterSubject; level += 1) + ; // do nothing + for (; level < levelCnt; level += 1) { + let placements = placementsByLevel[level]; + let placement; + let searchIndex = binarySearch(placements, subjectPlacement.start, getCoordRangeEnd); + let lateralStart = searchIndex[0] + searchIndex[1]; // if exact match (which doesn't collide), go to next one + let lateralEnd = lateralStart; + while ( // loop through placements that horizontally intersect + (placement = placements[lateralEnd]) && // but not past the whole seg list + placement.start < subjectPlacement.end) { + lateralEnd += 1; + } + if (lateralStart < lateralEnd) { + return { level, lateralStart, lateralEnd }; + } + } + return null; + } + function stretchWeb(topLevelNodes, totalThickness) { + const stretchNode = cacheable((node, startCoord, prevThickness) => getEventKey(node), (node, startCoord, prevThickness) => { + let { nextLevelNodes, thickness } = node; + let allThickness = thickness + prevThickness; + let thicknessFraction = thickness / allThickness; + let endCoord; + let newChildren = []; + if (!nextLevelNodes.length) { + endCoord = totalThickness; + } + else { + for (let childNode of nextLevelNodes) { + if (endCoord === undefined) { + let res = stretchNode(childNode, startCoord, allThickness); + endCoord = res[0]; + newChildren.push(res[1]); + } + else { + let res = stretchNode(childNode, endCoord, 0); + newChildren.push(res[1]); + } + } + } + let newThickness = (endCoord - startCoord) * thicknessFraction; + return [endCoord - newThickness, { + ...node, + thickness: newThickness, + nextLevelNodes: newChildren, + }]; + }); + return topLevelNodes.map((node) => stretchNode(node, 0, 0)[1]); + } + // not sorted in any particular order + function webToRects(topLevelNodes) { + let rectMap = new Map(); + /* + Returns max stackForward of the node's forward children + */ + const processNode = cacheable((node, levelCoord, stackDepth) => getEventKey(node), (node, levelCoord, stackDepth) => { + let rect = { + ...node, + levelCoord, + stackDepth, + stackForward: 0, // will assign after recursing + }; + rectMap.set(rect.eventRange.instance.instanceId, rect); + return (rect.stackForward = processNodes(node.nextLevelNodes, levelCoord + node.thickness, stackDepth + 1)); + }); + /* + Returns max stackForward of all `nodes` + */ + function processNodes(nodes, levelCoord, stackDepth) { + let stackForward = 0; + for (let node of nodes) { + stackForward = Math.max(processNode(node, levelCoord, stackDepth) + 1, stackForward); + } + return stackForward; + } + processNodes(topLevelNodes, 0, 0); + return rectMap; + } + // TODO: move to general util + function cacheable(keyFunc, workFunc) { + const cache = {}; + return (...args) => { + let key = keyFunc(...args); + return (key in cache) + ? cache[key] + : (cache[key] = workFunc(...args)); + }; + } + + const DEFAULT_TIME_FORMAT$1 = createFormatter({ + hour: 'numeric', + minute: '2-digit', + meridiem: false, + }); + class TimeGridEvent extends BaseComponent { + render() { + const { props } = this; + return (u$1(StandardEvent, { ...props, display: 'column', level: props.level, isNarrow: props.isNarrow, isShort: props.isShort, className: + // see note in TimeGridCol on why we use flexbox + props.isLiquid ? classNames.liquid : '', disableLiquid: !props.isLiquid, defaultTimeFormat: DEFAULT_TIME_FORMAT$1 })); + } + } + + class TimeGridMoreLink extends BaseComponent { + render() { + let { props } = this; + return (u$1("div", { className: joinClassNames(classNames.abs, classNames.flexCol), style: { + top: props.top, + height: props.height, + insetInlineEnd: 0, + zIndex: 9999, // HACK. move to className? + }, children: u$1(MoreLinkContainer, { className: classNames.liquid, display: 'column', allDayDate: null, segs: props.hiddenSegs, hiddenSegs: props.hiddenSegs, dateSpanProps: props.dateSpanProps, dateProfile: props.dateProfile, todayRange: props.todayRange, popoverContent: () => renderPlainFgSegs(props.hiddenSegs, props, /* isMirror = */ false), forceTimed: true, isNarrow: props.isNarrow, isMicro: props.isMicro }) })); + } + } + + const NowIndicatorDot = (props) => (u$1(ViewContextType.Consumer, { children: (context) => { + let { options } = context; + return (u$1("div", { className: joinClassNames(props.className, options.nowIndicatorDotClass), style: props.style })); + } })); + + const NowIndicatorLineContainer = (props) => (u$1(ViewContextType.Consumer, { children: (context) => { + let { options } = context; + let renderProps = { + date: context.dateEnv.toDate(props.date), + view: context.viewApi, + }; + return (u$1(ContentContainer, { elRef: props.elRef, tag: props.tag || 'div', attrs: props.attrs, className: props.className, style: props.style, renderProps: renderProps, generatorName: "nowIndicatorLineContent", customGenerator: options.nowIndicatorLineContent, classNameGenerator: options.nowIndicatorLineClass, didMount: options.nowIndicatorLineDidMount, willUnmount: options.nowIndicatorLineWillUnmount, children: props.children })); + } })); + + /* + Renders both the line AND the dot + TODO: DRY with other NowIndicator components + */ + function TimeGridNowIndicatorLine(props) { + const top = props.totalHeight != null + ? props.totalHeight * computeDateTopFrac(props.nowDate, props.dateProfile, props.dayDate) + : undefined; + return (u$1("div", { className: classNames.fill, style: { + zIndex: 2, // inlined from $now-indicator-z + pointerEvents: 'none', // TODO: className + }, children: [u$1(NowIndicatorLineContainer, { className: joinClassNames(classNames.fillX, classNames.noMarginX, classNames.borderlessX), style: { top }, date: props.nowDate }), (props.showDot ?? true) && (u$1(NowIndicatorDot, { className: joinClassNames(classNames.abs, classNames.start0), style: { top } }))] })); + } + + // Firefox is terrible at rendering absolute elements that span across multiple print pages + const isBrowserPrintQuirky = /* true || */ (typeof navigator !== 'undefined' && + navigator.userAgent.toLowerCase().includes('firefox')); + class TimeGridCol extends BaseComponent { + constructor() { + super(...arguments); + this.sortEventSegs = memoize(sortEventSegs); + this.getDateMeta = memoize(getDateMeta); + } + render() { + let { props, context } = this; + let { options, dateEnv } = context; + let isSelectMirror = options.selectMirror; + let mirrorSegs = // yuck + (props.eventDrag && props.eventDrag.segs) || + (props.eventResize && props.eventResize.segs) || + (isSelectMirror && props.dateSelectionSegs) || + []; + let dateMeta = this.getDateMeta(props.date, dateEnv, props.dateProfile, props.todayRange); + const baseClassName = joinClassNames(props.borderStart ? classNames.borderOnlyS : classNames.borderNone, props.width == null && classNames.liquid, classNames.rel); + const baseStyle = { + width: props.width, + zIndex: 1, // get above slots + }; + const isStack = this.getIsStack(); + const renderProps = { + ...dateMeta, + ...props.renderProps, + isStack, + isNarrow: props.isNarrow, + isMajor: props.isMajor, + view: context.viewApi, + }; + if (dateMeta.isDisabled) { + return (u$1("div", { role: 'gridcell', "aria-disabled": true, className: joinClassNames(generateClassName(options.dayLaneClass, renderProps), baseClassName), style: baseStyle })); + } + const innerClassName = joinClassNames(generateClassName(options.dayLaneInnerClass, renderProps), !isStack && classNames.fill); + const sortedFgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder); + return (u$1(ContentContainer, { tag: "div", attrs: { + ...props.attrs, + role: 'gridcell', + ...(dateMeta.isToday ? { 'aria-current': 'date' } : {}), + 'data-date': formatDayString(props.date), + }, className: baseClassName, style: baseStyle, renderProps: renderProps, generatorName: undefined, classNameGenerator: options.dayLaneClass, didMount: options.dayLaneDidMount, willUnmount: options.dayLaneWillUnmount, children: () => (u$1(S, { children: [this.renderFillSegs(props.businessHourSegs, 'non-business'), this.renderFillSegs(props.bgEventSegs, 'bg-event'), this.renderFillSegs(props.dateSelectionSegs, 'highlight'), u$1("div", { className: innerClassName, style: { zIndex: 1 }, children: this.renderFgSegs(sortedFgSegs, + /* isMirror = */ false) }), Boolean(mirrorSegs.length) && ( + // but only show it when there are actual mirror events, to avoid blocking clicks + u$1("div", { className: innerClassName, style: { zIndex: 1 }, children: this.renderFgSegs(mirrorSegs, + /* isMirror = */ true) })), this.renderNowIndicator(props.nowIndicatorSegs)] })) })); + } + renderFgSegs(sortedFgSegs, isMirror) { + const { props } = this; + if (this.getIsStack()) { + return renderPlainFgSegs(sortedFgSegs, props, isMirror); + } + return this.renderPositionedFgSegs(sortedFgSegs, isMirror); + } + renderPositionedFgSegs(segs, // if not mirror, needs to be sorted + isMirror) { + let { props, context } = this; + let { date, dateProfile, eventSelection, todayRange, nowDate } = props; + let { eventMaxStack, eventShortHeight, eventOrderStrict, eventMinHeight } = context.options; + // TODO: memoize this? + let segVerticals = computeFgSegVerticals(segs, dateProfile, date, props.slatCnt, props.slatHeight, eventMinHeight, eventShortHeight); + let [segRects, hiddenGroups] = buildWebPositioning(segs, segVerticals, eventOrderStrict, eventMaxStack); + return (u$1(S, { children: [segs.map((seg, index) => { + let { eventRange } = seg; + let { instanceId } = eventRange.instance; // guaranteed because it's an fg event + let segVertical = segVerticals[index] || {}; + let segRect = segRects.get(instanceId); // for horizontals. could be undefined!? HACK + let hStyle = (!isMirror && segRect) + ? this.computeSegHStyle(segRect) + : { left: 0, right: 0, zIndex: 0 }; + let isSelected = instanceId === eventSelection; + if (isSelected) { + hStyle.zIndex += 1000; // HACK: relies on hardcoded z-index offset; fragile if stacking context changes + } + let isDragging = Boolean(props.eventDrag && props.eventDrag.affectedInstances[instanceId]); + let isResizing = Boolean(props.eventResize && props.eventResize.affectedInstances[instanceId]); + let isInvisible = !isMirror && (isDragging || isResizing || !segRect); + return (u$1("div", { + // we would have used classNames.fill, but multi-page spanning breaks in Firefox + // we would have used height:100%, but multi-page spanning breaks in Safari + className: joinClassNames(classNames.abs, classNames.flexCol), style: { + visibility: isInvisible ? 'hidden' : undefined, + top: segVertical.start, + height: segVertical.size, + ...hStyle, + }, children: u$1(TimeGridEvent, { eventRange: eventRange, slicedStart: seg.startDate, slicedEnd: seg.endDate, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isMirror: isMirror, isSelected: isSelected, level: segRect ? segRect.stackDepth : 0, isNarrow: props.isNarrow, isShort: segVertical.isShort || false, isLiquid: true, ...getEventRangeMeta(eventRange, todayRange, nowDate) }) }, instanceId)); + }), this.renderHiddenGroups(hiddenGroups)] })); + } + /* + NOTE: will already have eventMinHeight applied because segEntries(?) already had it + */ + renderHiddenGroups(hiddenGroups) { + let { dateSpanProps, dateProfile, todayRange, nowDate, eventSelection, eventDrag, eventResize, isNarrow, isMicro } = this.props; + return (u$1(S, { children: hiddenGroups.map((hiddenGroup) => { + return (u$1(TimeGridMoreLink, { hiddenSegs: hiddenGroup.segs, top: hiddenGroup.start, height: hiddenGroup.end - hiddenGroup.start, isNarrow: isNarrow, isMicro: isMicro, dateSpanProps: dateSpanProps, dateProfile: dateProfile, todayRange: todayRange, nowDate: nowDate, eventSelection: eventSelection, eventDrag: eventDrag, eventResize: eventResize }, hiddenGroup.key)); + }) })); + } + renderFillSegs(segs, fillType) { + let { props, context } = this; + let segVerticals = computeFgSegVerticals(segs, props.dateProfile, props.date, props.slatCnt, props.slatHeight, context.options.eventMinHeight, context.options.eventShortHeight); + return (u$1(S, { children: segs.map((seg, index) => { + const { eventRange } = seg; + const segVertical = segVerticals[index] || {}; + return (u$1("div", { className: classNames.fillX, style: { + top: segVertical.start, + height: segVertical.size, + // HACK to get bg fills to overlap cell-start border + // which matches how dayGrid looks, + // which is important because all-day background events, in TimeGrid, + // will render on both at the same time + marginInlineStart: -1, + }, children: fillType === 'bg-event' ? + u$1(BgEvent, { eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isNarrow: props.isNarrow, isShort: segVertical.isShort || false, isVertical: true, ...getEventRangeMeta(eventRange, props.todayRange, props.nowDate) }) : + renderFill(fillType, context.options) }, buildEventRangeKey(eventRange))); + }) })); + } + renderNowIndicator(segs) { + let { props } = this; + if (props.forPrint || this.getIsStack()) { + return; + } + return segs.map((seg, i) => (u$1(TimeGridNowIndicatorLine, { nowDate: seg.startDate, dayDate: props.date, dateProfile: props.dateProfile, totalHeight: props.slatHeight != null ? props.slatHeight * props.slatCnt : undefined, showDot: seg.showDot ?? true }, i))); + } + /* + TODO: eventually move to width, not left+right + */ + computeSegHStyle(segRect) { + let { options } = this.context; + let shouldOverlap = options.slotEventOverlap; + let nearCoord = segRect.levelCoord; // the left side if LTR. the right side if RTL. floating-point + let farCoord = segRect.levelCoord + segRect.thickness; // the right side if LTR. the left side if RTL. floating-point + if (shouldOverlap) { + // double the width, but don't go beyond the maximum forward coordinate (1.0) + farCoord = Math.min(1, nearCoord + (farCoord - nearCoord) * 2); + } + let props = { + zIndex: segRect.stackDepth + 1, // convert from 0-base to 1-based + insetInlineStart: fracToCssDim(nearCoord), + insetInlineEnd: fracToCssDim(1 - farCoord), + marginInlineEnd: undefined, + }; + if (shouldOverlap && segRect.stackForward) { + // add padding to the edge so that forward stacked events don't cover the resizer's icon + props.marginInlineEnd = 10 * 2; // 10 is a guesstimate of the icon's width + } + return props; + } + getIsStack() { + const { eventPrintLayout } = this.context.options; + return this.props.forPrint && (eventPrintLayout === 'stack' || + (eventPrintLayout !== 'grid' /* aka 'auto' */ && isBrowserPrintQuirky)); + } + } + function renderPlainFgSegs(sortedFgSegs, { todayRange, nowDate, eventSelection, eventDrag, eventResize }, isMirror) { + return (u$1(S, { children: sortedFgSegs.map((seg) => { + let { eventRange } = seg; + let { instanceId } = eventRange.instance; + let isDragging = Boolean(eventDrag && eventDrag.affectedInstances[instanceId]); + let isResizing = Boolean(eventResize && eventResize.affectedInstances[instanceId]); + let isInvisible = isDragging || isResizing; + return (u$1("div", { className: classNames.breakInsideAvoid, style: { visibility: isInvisible ? 'hidden' : undefined }, children: u$1(TimeGridEvent, { eventRange: eventRange, slicedStart: seg.startDate, slicedEnd: seg.endDate, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isMirror: isMirror, isSelected: instanceId === eventSelection, level: 0, isShort: false, isNarrow: false, disableResizing: true, ...getEventRangeMeta(eventRange, todayRange, nowDate) }) }, instanceId)); + }) })); + } + + class TimeGridCols extends DateComponent { + constructor() { + super(...arguments); + // memo + this.processSlotOptions = memoize(processSlotOptions); + this.handleRootEl = (el) => { + this.rootEl = el; + if (el) { + this.context.registerInteractiveComponent(this, { + el, + isHitComboAllowed: this.props.isHitComboAllowed, + }); + } + else { + this.context.unregisterInteractiveComponent(this); + } + }; + } + render() { + const { props } = this; + return (u$1("div", { role: props.role /* !!! */, className: joinClassNames(props.className, classNames.flexRow), ref: this.handleRootEl, children: props.cells.map((cell, col) => (u$1(TimeGridCol, { dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, date: cell.date, isMajor: cell.isMajor, slatCnt: props.slatCnt, renderProps: cell.renderProps, attrs: cell.attrs, dateSpanProps: cell.dateSpanProps, forPrint: props.forPrint, borderStart: Boolean(col), isNarrow: props.cellIsNarrow, isMicro: props.cellIsMicro, + // content + fgEventSegs: props.fgEventSegsByCol[col], bgEventSegs: props.bgEventSegsByCol[col], businessHourSegs: props.businessHourSegsByCol[col], nowIndicatorSegs: props.nowIndicatorSegsByCol[col], dateSelectionSegs: props.dateSelectionSegsByCol[col], eventDrag: props.eventDragByCol[col], eventResize: props.eventResizeByCol[col], eventSelection: props.eventSelection, + // dimensions + width: props.colWidth, slatHeight: props.slatHeight }, cell.key))) })); + } + queryHit(isRtl, positionLeft, positionTop, elWidth) { + const { dateProfile, cells, colWidth, slatHeight } = this.props; + const { dateEnv, options } = this.context; + const { snapDuration, snapsPerSlot } = this.processSlotOptions(options.slotDuration, options.snapDuration); + const colCount = cells.length; + const { col, left, right } = computeColFromPosition(positionLeft, elWidth, colWidth, colCount, isRtl); + const cell = cells[col]; + const slatIndex = Math.floor(positionTop / slatHeight); + const slatTop = slatIndex * slatHeight; + const partial = (positionTop - slatTop) / slatHeight; // floating point number between 0 and 1 + const localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat + const snapIndex = slatIndex * snapsPerSlot + localSnapIndex; + const time = addDurations(dateProfile.slotMinTime, multiplyDuration(snapDuration, snapIndex)); + const start = dateEnv.add(cell.date, time); + const end = dateEnv.add(start, snapDuration); + return { + dateProfile, + dateSpan: { + range: { start, end }, + allDay: false, + ...cell.dateSpanProps, + }, + getDayEl: () => getCellEl(this.rootEl, col), + rect: { + left, + right, + top: slatTop, + bottom: slatTop + slatHeight, + }, + layer: 0, + }; + } + } + TimeGridCols.addPropsEquality({ + style: isPropsEqualShallow, + }); + // Utils + // ------------------------------------------------------------------------------------------------- + function processSlotOptions(slotDuration, snapDurationOverride) { + let snapDuration = snapDurationOverride || slotDuration; + let snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration); + if (snapsPerSlot === null) { + snapDuration = slotDuration; + snapsPerSlot = 1; + // TODO: say warning? + } + return { snapDuration, snapsPerSlot }; + } + + const NowIndicatorHeaderContainer = (props) => (u$1(ViewContextType.Consumer, { children: (context) => { + let { options } = context; + let renderProps = { + date: context.dateEnv.toDate(props.date), + view: context.viewApi, + }; + return (u$1(ContentContainer, { elRef: props.elRef, tag: props.tag || 'div', attrs: props.attrs, className: props.className, style: props.style, renderProps: renderProps, generatorName: "nowIndicatorHeaderContent", customGenerator: options.nowIndicatorHeaderContent, classNameGenerator: options.nowIndicatorHeaderClass, didMount: options.nowIndicatorHeaderDidMount, willUnmount: options.nowIndicatorHeaderWillUnmount, children: props.children })); + } })); + + /* + TODO: DRY with other NowIndicator components + */ + function TimeGridNowIndicatorArrow(props) { + return (u$1("div", { + // crop any overflow that the arrow/line might cause + // TODO: just do this on the entire canvas within the scroller + className: joinClassNames(classNames.fill, classNames.crop), style: { + zIndex: 2, // inlined from $now-indicator-z + pointerEvents: 'none', // TODO: className + }, children: u$1(NowIndicatorHeaderContainer, { className: classNames.abs, style: { + top: props.totalHeight != null + ? props.totalHeight * computeDateTopFrac(props.nowDate, props.dateProfile) + : undefined + }, date: props.nowDate }) })); + } + + const DEFAULT_SLAT_LABEL_FORMAT = createFormatter({ + hour: 'numeric', + minute: '2-digit', + omitZeroMinute: true, + meridiem: 'short', + }); + /* + Always oriented in a column + */ + class TimeGridSlatHeader extends BaseComponent { + constructor() { + super(...arguments); + // memo + this.createRenderProps = memoize(createRenderProps); + // ref + this.innerElRef = M$1(); + } + render() { + let { props, context } = this; + let { options } = context; + let headerFormat = // TODO: fully pre-parse + options.slotHeaderFormat == null ? DEFAULT_SLAT_LABEL_FORMAT : + Array.isArray(options.slotHeaderFormat) ? createFormatter(options.slotHeaderFormat[0]) : + createFormatter(options.slotHeaderFormat); + let renderProps = this.createRenderProps(props.date, props.time, !props.isLabeled, props.isNarrow, props.isFirst, headerFormat, context); + let className = joinClassNames(props.liquidHeight && classNames.liquid, classNames.flexRow, classNames.alignStart, classNames.noMargin, classNames.noPadding, props.borderTop ? classNames.borderOnlyT : classNames.borderNone); + if (!props.isLabeled) { + return (u$1("div", { className: joinClassNames(generateClassName(options.slotHeaderClass, renderProps), className), style: { + height: props.height, + } })); + } + return (u$1(ContentContainer, { tag: "div", attrs: { + 'data-time': props.isoTimeStr, + }, style: { + height: props.height, + }, className: className, renderProps: renderProps, generatorName: "slotHeaderContent", customGenerator: options.slotHeaderContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotHeaderClass, didMount: options.slotHeaderDidMount, willUnmount: options.slotHeaderWillUnmount, children: (InnerContent) => (u$1("div", { ref: this.innerElRef, className: joinClassNames(classNames.noShrink, classNames.whiteSpaceNoWrap, classNames.flexRow), children: u$1(InnerContent, { tag: "div", className: generateClassName(options.slotHeaderInnerClass, renderProps) }) })) })); + } + componentDidMount() { + this._isUnmounting = false; + const { props } = this; + const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect + if (innerEl) { // could be null if !isLabeled + // TODO: only attach this if refs props present + // TODO: fire width/height independently? + this.disconnectInnerSize = watchSize(innerEl, (width, height) => { + if (this._isUnmounting) + return; + setRef(props.innerWidthRef, width); + setRef(props.innerHeightRef, height); + }); + } + } + componentWillUnmount() { + const { props } = this; + this._isUnmounting = true; + if (this.disconnectInnerSize) { + this.disconnectInnerSize(); + setRef(props.innerWidthRef, null); + setRef(props.innerHeightRef, null); + } + } + } + function createRenderProps(date, time, isMinor, isNarrow, isFirst, headerFormat, context) { + return { + // this is a time-specific slot. not day-specific, so don't do today/nowRange + ...getDateMeta(date, context.dateEnv), + level: 0, // axis level (for when multiple axes) + text: joinDateTimeFormatParts(context.dateEnv.formatToParts(date, headerFormat)), + time: time, + isMajor: false, + isMinor, + isTime: true, + isNarrow, + hasNavLink: false, + isFirst, + view: context.viewApi, + }; + } + function renderInnerContent(props) { + return props.text; + } + + class TimeGridSlatLane extends BaseComponent { + constructor() { + super(...arguments); + // memo + this.getDateMeta = memoize(getDateMeta); + } + render() { + let { props, context } = this; + let { options } = context; + let renderProps = { + // this is a time-specific slot. not day-specific, so don't do today/nowRange + ...this.getDateMeta(props.date, context.dateEnv), + time: props.time, + isMajor: false, + isMinor: !props.isLabeled, + view: context.viewApi, + }; + return (u$1(ContentContainer, { tag: "div", attrs: { + 'data-time': props.isoTimeStr, + }, className: joinClassNames(classNames.noMargin, classNames.noPadding, classNames.liquid, props.borderTop ? classNames.borderOnlyT : classNames.borderNone), renderProps: renderProps, generatorName: undefined, classNameGenerator: options.slotLaneClass, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount })); + } + } + + const DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' }); + class TimeGridWeekNumber extends BaseComponent { + constructor() { + super(...arguments); + // ref + this.innerElRef = M$1(); + } + render() { + let { props, context } = this; + let { options, dateEnv } = context; + let range = props.dateProfile.renderRange; + let dayCnt = diffDays(range.start, range.end); + // HACK: only make week-number a nav-link when NOT in week-view + let hasNavLink = dayCnt === 1 && options.navLinks; + let weekDateMarker = range.start; + let fullDateStr = buildDateStr(context, weekDateMarker, 'week'); + let weekNum = dateEnv.computeWeekNumber(weekDateMarker); + let weekTextParts = dateEnv.formatToParts(weekDateMarker, options.weekNumberFormat || DEFAULT_WEEK_NUM_FORMAT); + let weekText = joinDateTimeFormatParts(weekTextParts); + let weekDateZoned = dateEnv.toDate(weekDateMarker); + const weekNumberRenderProps = { + num: weekNum, + text: weekText, + textParts: weekTextParts, + date: weekDateZoned, + isNarrow: props.isNarrow, + hasNavLink, + options: { dayMinWidth: options.dayMinWidth }, + }; + return (u$1(ContentContainer, { tag: 'div', attrs: { + role: 'gridcell', // doesn't always describe other cells in row, so make generic + 'aria-label': fullDateStr, + }, className: joinClassNames(classNames.flexRow, classNames.noMargin, classNames.noPadding, props.isLiquid ? classNames.liquid : classNames.contentBox), style: { + width: props.width, + }, renderProps: weekNumberRenderProps, generatorName: "weekNumberHeaderContent", customGenerator: options.weekNumberHeaderContent, defaultGenerator: renderText$1, classNameGenerator: options.weekNumberHeaderClass, didMount: options.weekNumberHeaderDidMount, willUnmount: options.weekNumberHeaderWillUnmount, children: (InnerContent) => (u$1("div", { ref: this.innerElRef, className: joinClassNames(classNames.flexRow, classNames.noShrink, classNames.whiteSpaceNoWrap), children: u$1(InnerContent, { tag: 'div', attrs: hasNavLink + ? buildNavLinkAttrs(context, range.start, 'week', fullDateStr) + : { 'aria-label': fullDateStr }, className: generateClassName(options.weekNumberHeaderInnerClass, weekNumberRenderProps) }) })) })); + } + componentDidMount() { + this._isUnmounting = false; + const { props } = this; + const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect + // TODO: only attach this if refs props present + // TODO: handle width/height independently? + this.disconnectInnerSize = watchSize(innerEl, (width, height) => { + if (this._isUnmounting) + return; + setRef(props.innerWidthRef, width); + setRef(props.innerHeightRef, height); + }); + } + componentWillUnmount() { + const { props } = this; + this._isUnmounting = true; + this.disconnectInnerSize(); + setRef(props.innerWidthRef, null); + setRef(props.innerHeightRef, null); + } + } + + function TimeGridAxisEmpty(props) { + return (u$1("div", { role: 'gridcell' // is empty so can't be rowheader/columnheader + , className: props.isLiquid ? classNames.liquid : classNames.contentBox, style: { width: props.width } })); + } + + class TimeGridLayoutPannable extends BaseComponent { + constructor() { + super(...arguments); + this.state = { + headerTierHeights: [], + }; + // refs + this.headerLabelInnerWidthRefMap = new RefMap(() => { + afterSize(this.handleAxisWidths); + }); + this.headerLabelInnerHeightRefMap = new RefMap(() => { + afterSize(this.handleHeaderHeights); + }); + this.headerMainInnerHeightRefMap = new RefMap(() => { + afterSize(this.handleHeaderHeights); + }); + this.handleAllDayLabelInnerWidth = (width) => { + this.allDayLabelInnerWidth = width; + afterSize(this.handleAxisWidths); + }; + this.slatLabelInnerWidthRefMap = new RefMap(() => { + afterSize(this.handleAxisWidths); + }); + this.slatLabelInnerHeightRefMap = new RefMap(() => { + afterSize(this.handleSlatInnerHeights); + }); + this.headerScrollerRef = M$1(); + this.allDayScrollerRef = M$1(); + this.mainScrollerRef = M$1(); + this.footScrollerRef = M$1(); + this.axisScrollerRef = M$1(); + // Sizing + // ----------------------------------------------------------------------------------------------- + this.handleTotalWidth = (totalWidth) => { + if (this._isUnmounting) + return; + this.setState({ totalWidth }); + }; + this.handleBodyHeight = (bodyHeight) => { + if (this._isUnmounting) + return; + this.setState({ bodyHeight }); + }; + this.handleClientWidth = (clientWidth) => { + if (this._isUnmounting) + return; + this.setState({ clientWidth }); + }; + this.handleClientHeight = (clientHeight) => { + if (this._isUnmounting) + return; + this.setState({ clientHeight }); + }; + this.handleStickyBottomScrollbarWidth = (sticykBottomScrollbarWidth) => { + if (this._isUnmounting) + return; + this.setState({ sticykBottomScrollbarWidth }); + }; + this.handleHeaderHeights = () => { + if (this._isUnmounting) + return; + const headerLabelInnerHeightMap = this.headerLabelInnerHeightRefMap.current; + const headerMainInnerHeightMap = this.headerMainInnerHeightRefMap.current; + const heights = []; + // important to loop using 'main' because 'label' might not be tracking height if empty + for (const [tierNum, mainHeight] of headerMainInnerHeightMap.entries()) { + heights[tierNum] = Math.max(headerLabelInnerHeightMap.get(tierNum) || 0, mainHeight); + } + this.setState({ headerTierHeights: heights }); + }; + this.handleSlatInnerHeights = () => { + if (this._isUnmounting) + return; + const slatLabelInnerHeightMap = this.slatLabelInnerHeightRefMap.current; + let max = 0; + for (const slatLabelInnerHeight of slatLabelInnerHeightMap.values()) { + max = Math.max(max, slatLabelInnerHeight); + } + if (this.state.slatInnerHeight !== max) { + this.setState({ slatInnerHeight: max }); + } + }; + this.handleAxisWidths = () => { + if (this._isUnmounting) + return; + const headerLabelInnerWidthMap = this.headerLabelInnerWidthRefMap.current; + const slatLabelInnerWidthMap = this.slatLabelInnerWidthRefMap.current; + let max = this.allDayLabelInnerWidth || 0; // guard against all-day slot hidden + for (const headerLabelInnerWidth of headerLabelInnerWidthMap.values()) { + max = Math.max(max, headerLabelInnerWidth); + } + for (const slatLableInnerWidth of slatLabelInnerWidthMap.values()) { + max = Math.max(max, slatLableInnerWidth); + } + if (this.state.axisWidth !== max) { + this.setState({ axisWidth: max }); + } + }; + } + render() { + const { props, state, context, headerLabelInnerWidthRefMap, headerLabelInnerHeightRefMap, headerMainInnerHeightRefMap, slatLabelInnerWidthRefMap, slatLabelInnerHeightRefMap, } = this; + const { nowDate, headerTiers, forPrint } = props; + const nowTimeMs = nowDate.valueOf() - startOfDay(nowDate).valueOf(); + const { axisWidth, totalWidth, clientWidth, clientHeight, bodyHeight, sticykBottomScrollbarWidth } = state; + const { options } = context; + const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options); + const endScrollbarWidth = (totalWidth != null && clientWidth != null && axisWidth != null) + ? totalWidth - clientWidth - (axisWidth + 1) // +1 for hardcoded divider! + : undefined; + const verticalScrolling = !forPrint && !getIsHeightAuto(options); + const tableHeaderSticky = !forPrint && getTableHeaderSticky(options); + const footerScrollbarSticky = !forPrint && getFooterScrollbarSticky(options); + // TODO: DRY with getIsStack + const { eventPrintLayout } = options; + const printStackEnabled = (eventPrintLayout === 'stack' || + (eventPrintLayout !== 'grid' /* aka 'auto' */ && isBrowserPrintQuirky)); + const absPrint = forPrint && !printStackEnabled; + const simplePrint = forPrint && printStackEnabled; + const colCount = props.cells.length; + const [canvasWidth, colWidth] = computeColWidth(colCount, props.dayMinWidth, clientWidth); + const cellIsMicro = colWidth != null && colWidth <= dayMicroWidth; + const cellIsNarrow = cellIsMicro || (colWidth != null && colWidth <= options.dayNarrowWidth); + const slatCnt = props.slatMetas.length; + const [slatHeight, slatLiquidHeight] = computeSlatHeight(// TODO: memo? + verticalScrolling && options.expandRows, slatCnt, options.slotMinHeight, state.slatInnerHeight, clientHeight); + this.slatHeight = slatHeight; + // TODO: have computeSlatHeight return? + const totalSlatHeight = (slatHeight || 0) * slatCnt; + const forcedBodyHeight = absPrint ? totalSlatHeight : undefined; + const rowsNotExpanding = verticalScrolling && !options.expandRows && + clientHeight != null && clientHeight > totalSlatHeight; + const firstBodyRowIndex = options.dayHeaders ? headerTiers.length + 1 : 1; + const bottomScrollbarWidth = footerScrollbarSticky + ? sticykBottomScrollbarWidth + : (bodyHeight != null && clientHeight != null) + ? (bodyHeight - clientHeight) + : undefined; + return (u$1(S, { children: [options.dayHeaders && (u$1("div", { className: joinClassNames(generateClassName(options.tableHeaderClass, { + isSticky: tableHeaderSticky, + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: 0, + }), + // see note in TimeGridLayout about why we don't do classNames.printHeader + classNames.flexCol, tableHeaderSticky && classNames.tableHeaderSticky), style: { + zIndex: 1, + }, children: [u$1("div", { className: classNames.flexRow, children: [u$1("div", { role: 'rowgroup', className: classNames.contentBox, style: { width: axisWidth }, children: headerTiers.map((rowConfig, tierNum) => (u$1("div", { role: 'row', "aria-rowindex": tierNum + 1, className: joinClassNames(options.dayHeaderRowClass, classNames.flexRow, classNames.contentBox, tierNum < props.headerTiers.length - 1 + ? classNames.borderOnlyB + : classNames.borderNone), style: { + height: state.headerTierHeights[tierNum] + }, children: (options.weekNumbers && rowConfig.isDateRow) ? (u$1(TimeGridWeekNumber, { dateProfile: props.dateProfile, innerWidthRef: headerLabelInnerWidthRefMap.createRef(tierNum), innerHeightRef: headerLabelInnerHeightRefMap.createRef(tierNum), width: undefined, isLiquid: true, isNarrow: cellIsNarrow })) : (u$1(TimeGridAxisEmpty, { width: undefined, isLiquid: true })) }, tierNum))) }), u$1("div", { className: generateClassName(options.slotHeaderDividerClass, { + inTableHeader: true, + options: { dayMinWidth: options.dayMinWidth }, + }) }), u$1(Scroller, { horizontal: true, hideScrollbars: true, className: joinClassNames(classNames.flexRow, classNames.liquid), ref: this.headerScrollerRef, children: [u$1("div", { role: 'rowgroup', className: canvasWidth == null ? classNames.liquid : '', style: { width: canvasWidth }, children: props.headerTiers.map((rowConfig, tierNum) => (k$1(DayGridHeaderRow, { ...rowConfig, key: tierNum, role: 'row', rowIndex: tierNum, borderBottom: tierNum < props.headerTiers.length - 1, height: state.headerTierHeights[tierNum], colWidth: colWidth, viewportWidth: clientWidth, innerHeightRef: headerMainInnerHeightRefMap.createRef(tierNum), cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro, rowLevel: props.headerTiers.length - tierNum - 1 }))) }), Boolean(endScrollbarWidth) && (u$1("div", { className: joinClassNames(generateClassName(options.fillerClass, { inTableHeader: true }), classNames.borderOnlyS), style: { minWidth: endScrollbarWidth } }))] })] }), u$1("div", { className: generateClassName(options.dayHeaderDividerClass, { + isSticky: tableHeaderSticky, + multiMonthColumns: 0, + options: { allDaySlot: Boolean(options.allDaySlot) }, + }) })] })), u$1("div", { role: 'rowgroup', className: joinClassNames(generateClassName(options.tableBodyClass, { + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: 0, + }), classNames.flexCol, verticalScrolling && classNames.liquid, classNames.isolate), style: { + zIndex: 0, + }, children: [options.allDaySlot && (u$1(S, { children: [u$1("div", { role: 'row', "aria-rowindex": firstBodyRowIndex, className: classNames.flexRow, style: { zIndex: 1 }, children: [u$1(TimeGridAllDayHeader, { width: axisWidth, innerWidthRef: this.handleAllDayLabelInnerWidth, isNarrow: cellIsNarrow }), u$1("div", { className: generateClassName(options.slotHeaderDividerClass, { + inTableHeader: false, + options: { dayMinWidth: options.dayMinWidth }, + }) }), u$1(Scroller, { horizontal: true, hideScrollbars: true, + // fill remaining width + className: joinClassNames(classNames.flexRow, classNames.liquidX), ref: this.allDayScrollerRef, children: [u$1("div", { className: classNames.flexRow, style: { width: canvasWidth }, children: u$1(TimeGridAllDayLane, { dateProfile: props.dateProfile, todayRange: props.todayRange, cells: props.cells, showDayNumbers: false, forPrint: forPrint, isHitComboAllowed: props.isHitComboAllowed, className: joinClassNames(classNames.borderNone, classNames.liquidX), cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro, + // content + fgEventSegs: props.fgEventSegs, bgEventSegs: props.bgEventSegs, businessHourSegs: props.businessHourSegs, dateSelectionSegs: props.dateSelectionSegs, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows, + // dimensions + colWidth: colWidth }) }), Boolean(endScrollbarWidth) && (u$1("div", { className: joinClassNames(generateClassName(options.fillerClass, { inTableHeader: false }), classNames.borderOnlyS), style: { minWidth: endScrollbarWidth } }))] })] }), u$1("div", { className: joinClassNames(options.allDayDividerClass), style: { zIndex: 2 } })] })), u$1("div", { role: 'row', "aria-rowindex": firstBodyRowIndex + (options.allDaySlot ? 1 : 0), className: joinClassNames(classNames.flexRow, classNames.rel, // for Ruler.fillStart + verticalScrolling && classNames.liquid), style: { + zIndex: 0, + }, children: [u$1(Scroller, { vertical: verticalScrolling, hideScrollbars: true, className: joinClassNames(classNames.flexCol, classNames.contentBox), style: { + width: axisWidth, + }, ref: this.axisScrollerRef, clientHeightRef: this.handleBodyHeight, children: !simplePrint && (u$1(S, { children: u$1("div", { role: 'rowheader', "aria-label": options.timedText, className: joinClassNames(classNames.flexCol, classNames.grow, classNames.rel), style: { + height: forcedBodyHeight, + }, children: [u$1("div", { "aria-hidden": true, className: joinClassNames(classNames.flexCol, (verticalScrolling && options.expandRows) && classNames.grow, absPrint && classNames.fillX), children: props.slatMetas.map((slatMeta, slatI) => (k$1(TimeGridSlatHeader, { ...slatMeta /* FYI doesn't need isoTimeStr */, key: slatMeta.key, innerWidthRef: slatLabelInnerWidthRefMap.createRef(slatMeta.key), innerHeightRef: slatLabelInnerHeightRefMap.createRef(slatMeta.key), borderTop: Boolean(slatI), isNarrow: cellIsNarrow, height: slatLiquidHeight ? undefined : slatHeight, liquidHeight: slatLiquidHeight }))) }), !forPrint && options.nowIndicator && rangeContainsMarker(props.dateProfile.currentRange, nowDate) && + nowTimeMs >= props.dateProfile.slotMinTime.milliseconds && + nowTimeMs < props.dateProfile.slotMaxTime.milliseconds && (u$1(TimeGridNowIndicatorArrow, { nowDate: nowDate, dateProfile: props.dateProfile, totalHeight: slatHeight != null ? slatHeight * slatCnt : undefined })), Boolean(rowsNotExpanding || bottomScrollbarWidth) && (u$1("div", { className: joinClassNames(generateClassName(options.fillerClass, { inTableHeader: false }), classNames.borderOnlyT, rowsNotExpanding && classNames.liquid), style: { + minHeight: bottomScrollbarWidth + } }))] }) })) }), u$1("div", { className: generateClassName(options.slotHeaderDividerClass, { + inTableHeader: false, + options: { dayMinWidth: options.dayMinWidth }, + }) }), u$1("div", { + // we need this div because it's bad for Scroller to have left/right borders, + // AND because we need to containt the FooterScrollbar + className: joinClassNames(classNames.flexCol, classNames.liquid), children: [u$1(Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars: footerScrollbarSticky || // also means height:auto, so won't need vertical scrollbars anyway + forPrint, className: joinClassNames(classNames.flexCol, classNames.rel, // for Ruler.fillStart + verticalScrolling && classNames.liquid), ref: this.mainScrollerRef, clientWidthRef: this.handleClientWidth, clientHeightRef: this.handleClientHeight, children: u$1("div", { className: joinClassNames(classNames.flexCol, classNames.grow, classNames.rel), style: { + width: canvasWidth, + height: forcedBodyHeight, + }, children: [u$1(TimeGridCols, { dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, cells: props.cells, slatCnt: slatCnt, forPrint: forPrint, isHitComboAllowed: props.isHitComboAllowed, className: simplePrint ? '' : classNames.fill, + // content + fgEventSegsByCol: props.fgEventSegsByCol, bgEventSegsByCol: props.bgEventSegsByCol, businessHourSegsByCol: props.businessHourSegsByCol, nowIndicatorSegsByCol: props.nowIndicatorSegsByCol, dateSelectionSegsByCol: props.dateSelectionSegsByCol, eventDragByCol: props.eventDragByCol, eventResizeByCol: props.eventResizeByCol, eventSelection: props.eventSelection, + // dimensions + colWidth: colWidth, slatHeight: slatHeight, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro }), !simplePrint && (u$1(S, { children: [u$1("div", { "aria-hidden": true, className: joinClassNames(classNames.flexCol, (verticalScrolling && options.expandRows) && classNames.grow, absPrint ? classNames.fillX : classNames.rel), children: props.slatMetas.map((slatMeta, slatI) => (u$1("div", { className: joinClassNames(classNames.flexRow, slatLiquidHeight && classNames.liquid), style: { + height: slatLiquidHeight ? '' : slatHeight + }, children: k$1(TimeGridSlatLane, { ...slatMeta /* FYI doesn't need isoTimeStr */, key: slatMeta.key, borderTop: Boolean(slatI) }) }, slatMeta.key))) }), rowsNotExpanding && (u$1("div", { className: joinClassNames(generateClassName(options.fillerClass, { inTableHeader: false }), classNames.borderOnlyT, classNames.liquid) }))] }))] }) }), Boolean(footerScrollbarSticky) && (u$1(FooterScrollbar, { isSticky: true, canvasWidth: canvasWidth, scrollerRef: this.footScrollerRef, scrollbarWidthRef: this.handleStickyBottomScrollbarWidth }))] })] })] }), u$1(Ruler, { widthRef: this.handleTotalWidth })] })); + } + // Lifecycle + // ----------------------------------------------------------------------------------------------- + componentDidMount() { + this._isUnmounting = false; + this.initScrollers(); + this.updateSlatHeight(); + } + componentDidUpdate() { + this.updateScrollers(); + this.updateSlatHeight(); + } + componentWillUnmount() { + this._isUnmounting = true; + this.destroyScrollers(); + this.prevSlatHeight = undefined; + setRef(this.props.slatHeightRef, null); + } + updateSlatHeight() { + if (this.prevSlatHeight !== this.slatHeight) { + setRef(this.props.slatHeightRef, this.prevSlatHeight = this.slatHeight); + } + } + // Scrolling + // ----------------------------------------------------------------------------------------------- + initScrollers() { + const ScrollerSyncer = getScrollerSyncerClass(this.context.pluginHooks); + this.dayScroller = new ScrollerSyncer(true); // horizontal=true + this.timeScroller = new ScrollerSyncer(); // horizontal=false + setRef(this.props.dayScrollerRef, this.dayScroller); + setRef(this.props.timeScrollerRef, this.timeScroller); + this.updateScrollers(); + } + updateScrollers() { + this.dayScroller.handleChildren([ + this.headerScrollerRef.current, + this.allDayScrollerRef.current, + this.mainScrollerRef.current, + this.footScrollerRef.current, + ]); + this.timeScroller.handleChildren([ + this.axisScrollerRef.current, + this.mainScrollerRef.current, + ]); + } + destroyScrollers() { + setRef(this.props.dayScrollerRef, null); + setRef(this.props.timeScrollerRef, null); + } + } + TimeGridLayoutPannable.addPropsEquality({ + headerTierHeights: isArraysEqual, + }); + + class TimeGridLayoutNormal extends BaseComponent { + constructor() { + super(...arguments); + this.state = {}; + // refs + this.headerLabelInnerWidthRefMap = new RefMap(() => { + afterSize(this.handleAxisInnerWidths); + }); + this.handleAllDayLabelInnerWidth = (width) => { + this.allDayLabelInnerWidth = width; + afterSize(this.handleAxisInnerWidths); + }; + this.handleWeekNumberInnerWidth = (width) => { + this.weekNumberInnerWidth = width; + afterSize(this.handleAxisInnerWidths); + }; + this.slatLabelInnerWidthRefMap = new RefMap(() => { + afterSize(this.handleAxisInnerWidths); + }); + this.slatLabelInnerHeightRefMap = new RefMap(() => { + afterSize(this.handleSlatInnerHeights); + }); + // Sizing + // ----------------------------------------------------------------------------------------------- + this.handleTotalWidth = (totalWidth) => { + if (this._isUnmounting) + return; + // Must delay the rerender because might change the width of the all-day DayGridRow events, + // which shows a ResizeObserver loop warning + requestAnimationFrame(() => { + if (this._isUnmounting) + return; + this.setState({ totalWidth }); + }); + }; + this.handleClientWidth = (clientWidth) => { + if (this._isUnmounting) + return; + this.setState({ clientWidth }); + }; + this.handleClientHeight = (clientHeight) => { + if (this._isUnmounting) + return; + this.setState({ clientHeight }); + }; + this.handleAxisInnerWidths = () => { + if (this._isUnmounting) + return; + const headerLabelInnerWidthMap = this.headerLabelInnerWidthRefMap.current; + const slatLabelInnerWidthMap = this.slatLabelInnerWidthRefMap.current; + let max = Math.max(this.weekNumberInnerWidth || 0, // might not exist + this.allDayLabelInnerWidth || 0 // guard against all-day slot hidden + ); + for (const headerLabelInnerWidth of headerLabelInnerWidthMap.values()) { + max = Math.max(max, headerLabelInnerWidth); + } + for (const slatLabelInnerWidth of slatLabelInnerWidthMap.values()) { + max = Math.max(max, slatLabelInnerWidth); + } + if (this.state.axisWidth !== max) { + this.setState({ axisWidth: max }); + } + }; + this.handleSlatInnerHeights = () => { + if (this._isUnmounting) + return; + const slatLabelInnerHeightMap = this.slatLabelInnerHeightRefMap.current; + let max = 0; + for (const slatLabelInnerHeight of slatLabelInnerHeightMap.values()) { + max = Math.max(max, slatLabelInnerHeight); + } + if (this.state.slatInnerHeight !== max) { + this.setState({ slatInnerHeight: max }); + } + }; + } + render() { + const { props, state, context, slatLabelInnerWidthRefMap, slatLabelInnerHeightRefMap, headerLabelInnerWidthRefMap } = this; + const { nowDate, forPrint } = props; + const nowTimeMs = nowDate.valueOf() - startOfDay(nowDate).valueOf(); + const { axisWidth, clientWidth, totalWidth } = state; + const { options } = context; + const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options); + const endScrollbarWidth = (totalWidth != null && clientWidth != null && !forPrint) + ? totalWidth - clientWidth + : undefined; + const verticalScrolling = !forPrint && !getIsHeightAuto(options); + const tableHeaderSticky = !forPrint && getTableHeaderSticky(options); + const slatCnt = props.slatMetas.length; + const [slatHeight, slatLiquidHeight] = computeSlatHeight(verticalScrolling && options.expandRows, slatCnt, options.slotMinHeight, state.slatInnerHeight, state.clientHeight); + this.slatHeight = slatHeight; + // TODO: have computeSlatHeight return? + const totalSlatHeight = (slatHeight || 0) * slatCnt; + const rowsNotExpanding = verticalScrolling && !options.expandRows && + state.clientHeight != null && state.clientHeight > totalSlatHeight; + // TODO: DRY with getIsStack + const { eventPrintLayout } = options; + const printStackEnabled = (eventPrintLayout === 'stack' || + (eventPrintLayout !== 'grid' /* aka 'auto' */ && isBrowserPrintQuirky)); + const absPrint = forPrint && !printStackEnabled; + const simplePrint = forPrint && printStackEnabled; + // for printing + // in Chrome, slats and columns both need abs positioning within a relative container for them + // to sync across pages, and the relative container needs an explicit height + // in Firefox, same applies, but the flex-row for the cells has trouble spanning across page, + // so we need to set explicit height on flex-row and all parents + const forcedBodyHeight = absPrint ? totalSlatHeight : undefined; + const colCount = props.cells.length; + const colWidth = clientWidth != null ? clientWidth / colCount : undefined; + const cellIsMicro = colWidth != null && colWidth <= dayMicroWidth; + const cellIsNarrow = cellIsMicro || (colWidth != null && colWidth <= options.dayNarrowWidth); + return (u$1(S, { children: [options.dayHeaders && (u$1("div", { role: 'rowgroup', className: joinClassNames(generateClassName(options.tableHeaderClass, { + isSticky: tableHeaderSticky, + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: 0, + }), + // see note in TimeGridLayout about why we don't do classNames.printHeader + classNames.flexCol, tableHeaderSticky && classNames.tableHeaderSticky), style: { + zIndex: 1, + }, children: [props.headerTiers.map((rowConfig, tierNum) => (u$1("div", { role: 'row', className: classNames.flexRow, children: [u$1("div", { className: joinClassNames(options.dayHeaderRowClass, classNames.flexRow, tierNum < props.headerTiers.length - 1 + ? classNames.borderOnlyB + : classNames.borderNone), children: (options.weekNumbers && rowConfig.isDateRow) ? (u$1(TimeGridWeekNumber, { dateProfile: props.dateProfile, innerWidthRef: this.handleWeekNumberInnerWidth, innerHeightRef: headerLabelInnerWidthRefMap.createRef(tierNum), width: axisWidth, isLiquid: false, isNarrow: cellIsNarrow })) : (u$1(TimeGridAxisEmpty, { width: axisWidth, isLiquid: false })) }), u$1("div", { className: generateClassName(options.slotHeaderDividerClass, { + inTableHeader: true, + options: { dayMinWidth: options.dayMinWidth }, + }) }), u$1(DayGridHeaderRow, { ...rowConfig, className: classNames.liquid, borderBottom: tierNum < props.headerTiers.length - 1, viewportWidth: clientWidth, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro, rowLevel: props.headerTiers.length - tierNum - 1 }), Boolean(endScrollbarWidth) && (u$1("div", { className: joinClassNames(generateClassName(options.fillerClass, { inTableHeader: true }), classNames.borderOnlyS), style: { minWidth: endScrollbarWidth } }))] }, tierNum))), u$1("div", { className: generateClassName(options.dayHeaderDividerClass, { + isSticky: tableHeaderSticky, + multiMonthColumns: 0, + options: { allDaySlot: Boolean(options.allDaySlot) }, + }) })] })), u$1("div", { role: 'rowgroup', className: joinClassNames(generateClassName(options.tableBodyClass, { + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: 0, + }), classNames.flexCol, verticalScrolling && classNames.liquid, classNames.isolate), style: { + zIndex: 0, + }, children: [options.allDaySlot && (u$1(S, { children: [u$1("div", { role: 'row', className: classNames.flexRow, style: { zIndex: 1 }, children: [u$1(TimeGridAllDayHeader, { width: axisWidth, innerWidthRef: this.handleAllDayLabelInnerWidth, isNarrow: cellIsNarrow }), u$1("div", { className: generateClassName(options.slotHeaderDividerClass, { + inTableHeader: false, + options: { dayMinWidth: options.dayMinWidth }, + }) }), u$1(TimeGridAllDayLane, { dateProfile: props.dateProfile, todayRange: props.todayRange, cells: props.cells, showDayNumbers: false, forPrint: forPrint, isHitComboAllowed: props.isHitComboAllowed, className: joinClassNames(classNames.liquidX, classNames.borderNone), cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro, + // content + fgEventSegs: props.fgEventSegs, bgEventSegs: props.bgEventSegs, businessHourSegs: props.businessHourSegs, dateSelectionSegs: props.dateSelectionSegs, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection, dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows }), Boolean(endScrollbarWidth) && (u$1("div", { className: joinClassNames(generateClassName(options.fillerClass, { inTableHeader: false }), classNames.borderOnlyS), style: { minWidth: endScrollbarWidth } }))] }), u$1("div", { className: joinClassNames(options.allDayDividerClass), style: { zIndex: 2 } })] })), u$1(Scroller, { vertical: verticalScrolling, className: joinClassNames(classNames.flexCol, classNames.rel, // for Ruler.fillStart + verticalScrolling && classNames.liquid), style: { + zIndex: 0, + }, ref: props.timeScrollerRef, clientWidthRef: this.handleClientWidth, clientHeightRef: this.handleClientHeight, children: u$1("div", { className: joinClassNames(classNames.flexCol, classNames.grow, classNames.rel), style: { + // in print mode, this div creates the height and everything is absolutely positioned within + // we need to do this so that slats positioning synces with events's positioning + // otherwise, get out of sync on second page + height: forcedBodyHeight, + }, children: [u$1("div", { role: 'row', className: joinClassNames(classNames.flexRow, !simplePrint && classNames.fill), children: [u$1("div", { role: 'rowheader', "aria-label": options.timedText, className: classNames.contentBox, style: { width: axisWidth } }), u$1("div", { className: generateClassName(options.slotHeaderDividerClass, { + inTableHeader: false, + options: { dayMinWidth: options.dayMinWidth }, + }) }), u$1(TimeGridCols, { dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, cells: props.cells, slatCnt: slatCnt, forPrint: forPrint, isHitComboAllowed: props.isHitComboAllowed, className: classNames.liquid, + // content + fgEventSegsByCol: props.fgEventSegsByCol, bgEventSegsByCol: props.bgEventSegsByCol, businessHourSegsByCol: props.businessHourSegsByCol, nowIndicatorSegsByCol: props.nowIndicatorSegsByCol, dateSelectionSegsByCol: props.dateSelectionSegsByCol, eventDragByCol: props.eventDragByCol, eventResizeByCol: props.eventResizeByCol, eventSelection: props.eventSelection, + // dimensions + slatHeight: slatHeight, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro })] }), !simplePrint && (u$1(S, { children: [u$1("div", { "aria-hidden": true, className: joinClassNames(classNames.flexCol, (verticalScrolling && options.expandRows) && classNames.grow, absPrint + ? classNames.fillX // will assume top:0, height will be decided naturally + : classNames.rel), children: props.slatMetas.map((slatMeta, slatI) => (u$1("div", { className: joinClassNames(slatLiquidHeight && classNames.liquid, classNames.flexRow), style: { + height: slatLiquidHeight ? undefined : slatHeight + }, children: [u$1("div", { + // the pannable version of TimeGrid has axis labels all consecutive in one column + // simulate this for the non-pannable version + className: classNames.flexCol, style: { width: axisWidth }, children: k$1(TimeGridSlatHeader, { ...slatMeta /* FYI doesn't need isoTimeStr */, key: slatMeta.key, innerWidthRef: slatLabelInnerWidthRefMap.createRef(slatMeta.key), innerHeightRef: slatLabelInnerHeightRefMap.createRef(slatMeta.key), borderTop: Boolean(slatI), isNarrow: cellIsNarrow }) }), u$1("div", { className: generateClassName(options.slotHeaderDividerClass, { + inTableHeader: false, + options: { dayMinWidth: options.dayMinWidth }, + }), style: { visibility: 'hidden' } }), k$1(TimeGridSlatLane, { ...slatMeta /* FYI doesn't need isoTimeStr */, key: slatMeta.key, borderTop: Boolean(slatI) })] }, slatMeta.key))) }), rowsNotExpanding && (u$1("div", { className: joinClassNames(generateClassName(options.fillerClass, { inTableHeader: false }), classNames.borderOnlyT, classNames.liquid) })), !forPrint && options.nowIndicator && rangeContainsMarker(props.dateProfile.currentRange, nowDate) && + nowTimeMs >= props.dateProfile.slotMinTime.milliseconds && + nowTimeMs < props.dateProfile.slotMaxTime.milliseconds && (u$1(TimeGridNowIndicatorArrow, { nowDate: nowDate, dateProfile: props.dateProfile, totalHeight: slatHeight != null ? slatHeight * slatCnt : undefined }))] }))] }) })] }), u$1(Ruler, { widthRef: this.handleTotalWidth })] })); + } + // Lifecycle + // ----------------------------------------------------------------------------------------------- + componentDidMount() { + this._isUnmounting = false; + this.updateSlatHeight(); + } + componentDidUpdate() { + this.updateSlatHeight(); + } + componentWillUnmount() { + this._isUnmounting = true; + this.prevSlatHeight = undefined; + setRef(this.props.slatHeightRef, null); + } + updateSlatHeight() { + if (this.prevSlatHeight !== this.slatHeight) { + setRef(this.props.slatHeightRef, this.prevSlatHeight = this.slatHeight); + } + } + } + + function buildEmptySegCols(segsByCol) { + return segsByCol.map(() => []); + } + function buildEmptyInteractionCols(interactionsByCol) { + return interactionsByCol.map(() => null); + } + class TimeGridLayout extends BaseComponent { + constructor() { + super(...arguments); + // memo + this.buildSlatMetas = memoize(buildSlatMetas); + // refs + this.dayScrollerRef = M$1(); + this.timeScrollerRef = M$1(); + this.scrollState = {}; // updated in-place + // Sizing + // ----------------------------------------------------------------------------------------------- + this.handleSlatHeight = (slatHeight) => { + if (this._isUnmounting) + return; + this.slatHeight = slatHeight; + if (slatHeight != null) { + afterSize(this.applyTimeScroll); + } + }; + this.handleTimeScrollRequest = (scrollTime) => { + this.scrollState.time = scrollTime; + this.scrollState.y = undefined; + this.applyTimeScroll(); + }; + /* + Captures current values + */ + this.handleTimeScrollEnd = (isDevice) => { + if (isDevice) { + const y = this.timeScrollerRef.current.y; + // record, but only if not forPrint, which could give bogus values in the case of + // TimeGridLayoutPannable, which kills y-scrolling, but retains x-scrolling, + // which reports as a 0 y-scroll. + if (!this.props.forPrint) { + this.scrollState.y = y; + this.scrollState.time = undefined; + } + } + }; + this.applyTimeScroll = () => { + const timeScroller = this.timeScrollerRef.current; + const { slatHeight, scrollState } = this; + let { y, time } = scrollState; + if (y == null && + time && + slatHeight != null && + // Since applyTimeScroll is called by handleSlatHeight, could be called with null during cleanup, + // and the timeScroller might not exist + timeScroller) { + y = computeTimeTopFrac(time, this.props.dateProfile) + * (slatHeight * this.currentSlatCnt); + if (y) { + y++; // overcome top border + } + scrollState.y = y; // HACK: store raw pixel value + } + if (y != null) { + timeScroller.scrollTo({ y }); + } + }; + } + render() { + const { props, context } = this; + const { dateProfile } = props; + const { options, dateEnv } = context; + const { dayMinWidth } = options; + const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options); + const slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotHeaderInterval, options.slotDuration, dateEnv); + this.currentSlatCnt = slatMetas.length; + const businessHourSegs = props.forPrint ? [] : props.businessHourSegs; + const dateSelectionSegs = props.forPrint ? [] : props.dateSelectionSegs; + const eventDrag = props.forPrint ? null : props.eventDrag; + const eventResize = props.forPrint ? null : props.eventResize; + const businessHourSegsByCol = props.forPrint ? buildEmptySegCols(props.businessHourSegsByCol) : props.businessHourSegsByCol; + const dateSelectionSegsByCol = props.forPrint ? buildEmptySegCols(props.dateSelectionSegsByCol) : props.dateSelectionSegsByCol; + const eventDragByCol = props.forPrint ? buildEmptyInteractionCols(props.eventDragByCol) : props.eventDragByCol; + const eventResizeByCol = props.forPrint ? buildEmptyInteractionCols(props.eventResizeByCol) : props.eventResizeByCol; + const commonLayoutProps = { + dateProfile: dateProfile, + nowDate: props.nowDate, + todayRange: props.todayRange, + cells: props.cells, + slatMetas, + forPrint: props.forPrint, + isHitComboAllowed: props.isHitComboAllowed, + // header content + headerTiers: props.headerTiers, + // all-day content + fgEventSegs: props.fgEventSegs, + bgEventSegs: props.bgEventSegs, + businessHourSegs, + dateSelectionSegs, + eventDrag, + eventResize, + ...getAllDayMaxEventProps(options), + // timed content + fgEventSegsByCol: props.fgEventSegsByCol, + bgEventSegsByCol: props.bgEventSegsByCol, + businessHourSegsByCol, + nowIndicatorSegsByCol: props.nowIndicatorSegsByCol, + dateSelectionSegsByCol, + eventDragByCol, + eventResizeByCol, + // universal content + eventSelection: props.eventSelection, + // refs + timeScrollerRef: this.timeScrollerRef, + timeScrollState: this.scrollState, + slatHeightRef: this.handleSlatHeight, + borderlessX, + borderlessBottom, + }; + return (u$1(ViewContainer, { attrs: { + role: 'grid', + 'aria-colcount': props.cells.length, + 'aria-labelledby': props.labelId, + 'aria-label': props.labelStr, + }, className: joinClassNames(props.className, generateClassName(options.tableClass, { + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: 0, + }), + // we don't do classNames.printRoot/classNames.printHeader here because works poorly with print: + // - Firefox >85ish CAN have flexboxes within it, but those cannot do absolute positioning + // - Chrome works okay, but abs-positioned events cover the repeated header + // Also, there's weird padding on the last page at bottom of container, which matches + // the height of the repeated header + // - Safari was never able to do repeated headers in the first place + !props.forPrint && classNames.flexCol, classNames.isolate), viewSpec: context.viewSpec, children: dayMinWidth ? (u$1(TimeGridLayoutPannable, { ...commonLayoutProps, dayMinWidth: dayMinWidth, dayScrollerRef: this.dayScrollerRef })) : (u$1(TimeGridLayoutNormal, { ...commonLayoutProps })) })); + } + // Lifecycle + // ----------------------------------------------------------------------------------------------- + componentDidMount() { + this._isUnmounting = false; + this.resetScroll(); + this.context.emitter.on('_timeScrollRequest', this.handleTimeScrollRequest); + const timeScroller = this.timeScrollerRef.current; + if (timeScroller) { + timeScroller.addScrollEndListener(this.handleTimeScrollEnd); + } + } + componentDidUpdate(prevProps) { + if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) { + this.resetScroll(); + } + else if (prevProps.forPrint && !this.props.forPrint) { + // returning from print + // reapply scrolling because scroll-divs were probably restored + this.applyTimeScroll(); + } + } + componentWillUnmount() { + this._isUnmounting = true; + this.context.emitter.off('_timeScrollRequest', this.handleTimeScrollRequest); + const timeScroller = this.timeScrollerRef.current; + if (timeScroller) { + timeScroller.removeScrollEndListener(this.handleTimeScrollEnd); + } + } + // Scrolling + // ----------------------------------------------------------------------------------------------- + resetScroll() { + this.handleTimeScrollRequest(this.context.options.scrollTime); + // also resets day scroll + const dayScroller = this.dayScrollerRef.current; + if (dayScroller) { + dayScroller.scrollTo({ x: 0 }); + } + } + } + // Utils + // ----------------------------------------------------------------------------------------------- + const AUTO_ALL_DAY_MAX_EVENT_ROWS = 5; + function getAllDayMaxEventProps(options) { + let { dayMaxEvents, dayMaxEventRows } = options; + if (dayMaxEvents === true || dayMaxEventRows === true) { // is auto? + dayMaxEvents = undefined; + dayMaxEventRows = AUTO_ALL_DAY_MAX_EVENT_ROWS; // make sure "auto" goes to a real number + } + return { dayMaxEvents, dayMaxEventRows }; + } + + /* + An abstraction for a dragging interaction originating on an event. + Does higher-level things than PointerDragger, such as possibly: + - a "mirror" that moves with the pointer + - a minimum number of pixels or other criteria for a true drag to begin + + subclasses must emit: + - pointerdown + - dragstart + - dragmove + - pointerup + - dragend + */ + class ElementDragging { + constructor(el, selector) { + this.emitter = new Emitter(); + } + destroy() { + } + setMirrorIsVisible(bool) { + // optional if subclass doesn't want to support a mirror + } + setMirrorNeedsRevert(bool) { + // optional if subclass doesn't want to support a mirror + } + setAutoScrollEnabled(bool) { + // optional + } + } + + // TODO: get rid of this in favor of options system, + // tho it's really easy to access this globally rather than pass thru options. + const config = {}; + + // high-level segmenting-aware tester functions + // ------------------------------------------------------------------------------------------------------------------------ + function isInteractionValid(interaction, dateProfile, context) { + let { instances } = interaction.mutatedEvents; + for (let instanceId in instances) { + if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) { + return false; + } + } + return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions + } + function isDateSelectionValid(dateSelection, dateProfile, context) { + if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) { + return false; + } + return isNewPropsValid({ dateSelection }, context); + } + function isNewPropsValid(newProps, context) { + let calendarState = context.getCurrentData(); + let props = { + businessHours: calendarState.businessHours, + dateSelection: '', + eventStore: calendarState.eventStore, + eventUiBases: calendarState.eventUiBases, + eventSelection: '', + eventDrag: null, + eventResize: null, + ...newProps, + }; + return (context.pluginHooks.isPropsValid || isPropsValid)(props, context); + } + function isPropsValid(state, context, dateSpanMeta = {}, filterConfig) { + if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) { + return false; + } + if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) { + return false; + } + return true; + } + // Moving Event Validation + // ------------------------------------------------------------------------------------------------------------------------ + function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) { + let currentState = context.getCurrentData(); + let interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions + let subjectEventStore = interaction.mutatedEvents; + let subjectDefs = subjectEventStore.defs; + let subjectInstances = subjectEventStore.instances; + let subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ? + state.eventUiBases : + { '': currentState.selectionConfig }); + if (filterConfig) { + subjectConfigs = mapHash(subjectConfigs, filterConfig); + } + // exclude the subject events. TODO: exclude defs too? + let otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances); + let otherDefs = otherEventStore.defs; + let otherInstances = otherEventStore.instances; + let otherConfigs = compileEventUis(otherDefs, state.eventUiBases); + for (let subjectInstanceId in subjectInstances) { + let subjectInstance = subjectInstances[subjectInstanceId]; + let subjectRange = subjectInstance.range; + let subjectConfig = subjectConfigs[subjectInstance.defId]; + let subjectDef = subjectDefs[subjectInstance.defId]; + // constraint + if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) { + return false; + } + // overlap + let { eventOverlap } = context.options; + let eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null; + for (let otherInstanceId in otherInstances) { + let otherInstance = otherInstances[otherInstanceId]; + // intersect! evaluate + if (rangesIntersect(subjectRange, otherInstance.range)) { + let otherOverlap = otherConfigs[otherInstance.defId].overlap; + // consider the other event's overlap. only do this if the subject event is a "real" event + if (otherOverlap === false && interaction.isEvent) { + return false; + } + if (subjectConfig.overlap === false) { + return false; + } + if (eventOverlapFunc && !eventOverlapFunc(new EventImpl(context, otherDefs[otherInstance.defId], otherInstance), // still event + new EventImpl(context, subjectDef, subjectInstance))) { + return false; + } + } + } + // allow (a function) + let calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state + for (let subjectAllow of subjectConfig.allows) { + let subjectDateSpan = { + ...dateSpanMeta, + range: subjectInstance.range, + allDay: subjectDef.allDay, + }; + let origDef = calendarEventStore.defs[subjectDef.defId]; + let origInstance = calendarEventStore.instances[subjectInstanceId]; + let eventApi; + if (origDef) { // was previously in the calendar + eventApi = new EventImpl(context, origDef, origInstance); + } + else { // was an external event + eventApi = new EventImpl(context, subjectDef); // no instance, because had no dates + } + if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) { + return false; + } + } + } + return true; + } + // Date Selection Validation + // ------------------------------------------------------------------------------------------------------------------------ + function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) { + let relevantEventStore = state.eventStore; + let relevantDefs = relevantEventStore.defs; + let relevantInstances = relevantEventStore.instances; + let selection = state.dateSelection; + let selectionRange = selection.range; + let { selectionConfig } = context.getCurrentData(); + if (filterConfig) { + selectionConfig = filterConfig(selectionConfig); + } + // constraint + if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) { + return false; + } + // overlap + let { selectOverlap } = context.options; + let selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null; + for (let relevantInstanceId in relevantInstances) { + let relevantInstance = relevantInstances[relevantInstanceId]; + // intersect! evaluate + if (rangesIntersect(selectionRange, relevantInstance.range)) { + if (selectionConfig.overlap === false) { + return false; + } + if (selectOverlapFunc && !selectOverlapFunc(new EventImpl(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) { + return false; + } + } + } + // allow (a function) + for (let selectionAllow of selectionConfig.allows) { + let fullDateSpan = { ...dateSpanMeta, ...selection }; + if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) { + return false; + } + } + return true; + } + // Constraint Utils + // ------------------------------------------------------------------------------------------------------------------------ + function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) { + for (let constraint of constraints) { + if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) { + return false; + } + } + return true; + } + function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours + otherEventStore, // for if constraint is an even group ID + businessHoursUnexpanded, // for if constraint is 'businessHours' + context) { + if (constraint === 'businessHours') { + return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context)); + } + if (typeof constraint === 'string') { // an group ID + return eventStoreToRanges(filterEventStoreDefs(otherEventStore, (eventDef) => eventDef.groupId === constraint)); + } + if (typeof constraint === 'object' && constraint) { // non-null object + return eventStoreToRanges(expandRecurring(constraint, subjectRange, context)); + } + return []; // if it's false + } + // TODO: move to event-store file? + function eventStoreToRanges(eventStore) { + let { instances } = eventStore; + let ranges = []; + for (let instanceId in instances) { + ranges.push(instances[instanceId].range); + } + return ranges; + } + // TODO: move to geom file? + function anyRangesContainRange(outerRanges, innerRange) { + for (let outerRange of outerRanges) { + if (rangeContainsRange(outerRange, innerRange)) { + return true; + } + } + return false; + } + + function debounce(fn, ms) { + let timeoutStarted; + let timeoutAdded; + let timeoutId; // thruthiness indicates whether active timeout + function runWithTimeout(timeout) { + timeoutStarted = Date.now(); + timeoutAdded = 0; + timeoutId = setTimeout(() => { + if (timeoutAdded) { + runWithTimeout(timeoutAdded); + } + else { + timeoutId = undefined; + fn(); + } + }, timeout); + } + function request() { + if (timeoutId) { + timeoutAdded = Date.now() - timeoutStarted; + } + else { + runWithTimeout(ms); + } + } + function cancel() { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = undefined; + } + } + return [request, cancel]; + } + + class Store { + constructor() { + this.handlers = []; + } + set(value) { + this.currentValue = value; + for (let handler of this.handlers) { + handler(value); + } + } + subscribe(handler) { + this.handlers.push(handler); + if (this.currentValue !== undefined) { + handler(this.currentValue); + } + } + } + + /* + Subscribers will get a LIST of CustomRenderings + */ + class CustomRenderingStore extends Store { + constructor() { + super(...arguments); + this.map = new Map(); + } + // for consistent order + handle(customRendering) { + const { map } = this; + let updated = false; + if (customRendering.isActive) { + map.set(customRendering.id, customRendering); + updated = true; + } + else if (map.has(customRendering.id)) { + map.delete(customRendering.id); + updated = true; + } + if (updated) { + this.set(map); + } + } + } + + var protectedApi = /*#__PURE__*/Object.freeze({ + __proto__: null, + CustomRenderingStore: CustomRenderingStore, + debounce: debounce, + EventImpl: EventImpl, + buildEventRangeKey: buildEventRangeKey, + combineEventUis: combineEventUis, + compareByFieldSpecs: compareByFieldSpecs, + computeViewBorderless: computeViewBorderless, + computeVisibleDayRange: computeVisibleDayRange, + createEventUi: createEventUi, + createFormatter: createFormatter, + filterHash: filterHash, + flexibleCompare: flexibleCompare, + getEventKey: getEventKey, + getEventRangeMeta: getEventRangeMeta, + guid: guid, + identity: identity, + isArraysEqual: isArraysEqual, + isPropsEqualShallow: isPropsEqualShallow, + mapHash: mapHash, + mergeEventStores: mergeEventStores, + parseFieldSpecs: parseFieldSpecs, + refineClassName: refineClassName, + refineClassNameGenerator: refineClassNameGenerator, + refineProps: refineProps, + removeExact: removeExact, + sortEventSegs: sortEventSegs, + warn: warn, + CalendarApiImpl: CalendarApiImpl, + CalendarDataManager: CalendarDataManager, + CalendarInner: CalendarInner, + CalendarMediaRoot: CalendarMediaRoot, + computeRootClassName: computeRootClassName, + parseBusinessHours: parseBusinessHours, + BaseComponent: BaseComponent, + ContentContainer: ContentContainer, + RenderId: RenderId, + generateClassName: generateClassName, + getFooterScrollbarSticky: getFooterScrollbarSticky, + getIsHeightAuto: getIsHeightAuto, + getTableHeaderSticky: getTableHeaderSticky, + memoize: memoize, + memoizeObjArg: memoizeObjArg, + setRef: setRef, + computeEdges: computeEdges, + computeInnerRect: computeInnerRect, + getRectCenter: getRectCenter, + joinFuncishClassNames: joinFuncishClassNames, + mergeCalendarOptions: mergeCalendarOptions, + mergeContentInjectors: mergeContentInjectors, + mergeLifecycleCallbacks: mergeLifecycleCallbacks, + mergeViewOptionsMap: mergeViewOptionsMap, + applyStyleProp: applyStyleProp, + computeElIsRtl: computeElIsRtl, + AllDaySplitter: AllDaySplitter, + DayTimeColsSlicer: DayTimeColsSlicer, + NowIndicatorDot: NowIndicatorDot, + NowIndicatorHeaderContainer: NowIndicatorHeaderContainer, + NowIndicatorLineContainer: NowIndicatorLineContainer, + Splitter: Splitter, + TimeGridLayout: TimeGridLayout, + buildDayRanges: buildDayRanges, + buildTimeColsModel: buildTimeColsModel, + organizeSegsByCol: organizeSegsByCol, + splitInteractionByCol: splitInteractionByCol, + DateComponent: DateComponent, + DelayedRunner: DelayedRunner, + NowTimer: NowTimer, + Scroller: Scroller, + StandardEvent: StandardEvent, + ViewContainer: ViewContainer, + afterSize: afterSize, + buildNavLinkAttrs: buildNavLinkAttrs, + getDateMeta: getDateMeta, + watchHeight: watchHeight, + watchSize: watchSize, + watchWidth: watchWidth, + requestJson: requestJson, + unpromisify: unpromisify, + Emitter: Emitter, + DateProfileGenerator: DateProfileGenerator, + computeMajorUnit: computeMajorUnit, + isMajorUnit: isMajorUnit, + BgEvent: BgEvent, + DayTableModel: DayTableModel, + DayTableSlicer: DayTableSlicer, + MoreLinkContainer: MoreLinkContainer, + RefMap: RefMap, + Ruler: Ruler, + SegHierarchy: SegHierarchy, + Slicer: Slicer, + buildDateDataConfigs: buildDateDataConfigs, + buildDateRenderConfig: buildDateRenderConfig, + buildDateRowConfig: buildDateRowConfig, + buildDayTableModel: buildDayTableModel, + createDayHeaderFormatter: createDayHeaderFormatter, + groupIntersectingSegs: groupIntersectingSegs, + renderFill: renderFill, + ElementDragging: ElementDragging, + config: config, + isPropsValid: isPropsValid, + DayGridLayout: DayGridLayout, + FooterScrollbar: FooterScrollbar, + DateEnv: DateEnv, + addDays: addDays, + addMs: addMs, + asCleanDays: asCleanDays, + asRoughMinutes: asRoughMinutes, + asRoughMs: asRoughMs, + asRoughSeconds: asRoughSeconds, + createDuration: createDuration, + diffDayAndTime: diffDayAndTime, + diffWholeDays: diffWholeDays, + diffWholeWeeks: diffWholeWeeks, + formatDayString: formatDayString, + greatestDurationDenominator: greatestDurationDenominator, + intersectRanges: intersectRanges, + isInt: isInt, + isValidDate: isValidDate, + multiplyDuration: multiplyDuration, + padStart: padStart, + parseMarker: parse, + rangeContainsMarker: rangeContainsMarker, + rangesEqual: rangesEqual, + rangesIntersect: rangesIntersect, + startOfDay: startOfDay, + wholeDivideDurations: wholeDivideDurations + }); + + config.touchMouseIgnoreWait = 500; + let ignoreMouseDepth = 0; + let listenerCnt = 0; + let isWindowTouchMoveCancelled = false; + /* + Uses a "pointer" abstraction, which monitors UI events for both mouse and touch. + Tracks when the pointer "drags" on a certain element, meaning down+move+up. + + Also, tracks if there was touch-scrolling. + Also, can prevent touch-scrolling from happening. + Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement. + + emits: + - pointerdown + - pointermove + - pointerup + */ + class PointerDragging { + constructor(containerEl) { + this.subjectEl = null; + // options that can be directly assigned by caller + this.selector = ''; // will cause subjectEl in all emitted events to be this element + this.handleSelector = ''; + this.shouldIgnoreMove = false; + this.shouldWatchScroll = true; // for simulating pointermove on scroll + // internal states + this.isDragging = false; + this.isTouchDragging = false; + this.wasTouchScroll = false; // HACK public + // Mouse + // ---------------------------------------------------------------------------------------------------- + this.handleMouseDown = (ev) => { + if (!this.shouldIgnoreMouse() && + isPrimaryMouseButton(ev) && + this.tryStart(ev)) { + let pev = this.createEventFromMouse(ev, true); + this.emitter.trigger('pointerdown', pev); + this.initScrollWatch(pev); + if (!this.shouldIgnoreMove) { + document.addEventListener('mousemove', this.handleMouseMove); + } + document.addEventListener('mouseup', this.handleMouseUp); + } + }; + this.handleMouseMove = (ev) => { + let pev = this.createEventFromMouse(ev); + this.recordCoords(pev); + this.emitter.trigger('pointermove', pev); + }; + this.handleMouseUp = (ev) => { + document.removeEventListener('mousemove', this.handleMouseMove); + document.removeEventListener('mouseup', this.handleMouseUp); + this.emitter.trigger('pointerup', this.createEventFromMouse(ev)); + this.cleanup(); // call last so that pointerup has access to props + }; + // Touch + // ---------------------------------------------------------------------------------------------------- + this.handleTouchStart = (ev) => { + if (this.tryStart(ev)) { + this.isTouchDragging = true; + let pev = this.createEventFromTouch(ev, true); + this.emitter.trigger('pointerdown', pev); + this.initScrollWatch(pev); + // unlike mouse, need to attach to target, not document + // https://stackoverflow.com/a/45760014 + let targetEl = ev.target; + if (!this.shouldIgnoreMove) { + targetEl.addEventListener('touchmove', this.handleTouchMove); + } + targetEl.addEventListener('touchend', this.handleTouchEnd); + targetEl.addEventListener('touchcancel', this.handleTouchEnd); // treat it as a touch end + // attach a handler to get called when ANY scroll action happens on the page. + // this was impossible to do with normal on/off because 'scroll' doesn't bubble. + // http://stackoverflow.com/a/32954565/96342 + window.addEventListener('scroll', this.handleTouchScroll, true); + } + }; + this.handleTouchMove = (ev) => { + if (this.isDragging) { + let pev = this.createEventFromTouch(ev); + this.recordCoords(pev); + this.emitter.trigger('pointermove', pev); + } + }; + this.handleTouchEnd = (ev) => { + if (this.isDragging) { // done to guard against touchend followed by touchcancel + let targetEl = ev.target; + targetEl.removeEventListener('touchmove', this.handleTouchMove); + targetEl.removeEventListener('touchend', this.handleTouchEnd); + targetEl.removeEventListener('touchcancel', this.handleTouchEnd); + window.removeEventListener('scroll', this.handleTouchScroll, true); // useCaptured=true + this.emitter.trigger('pointerup', this.createEventFromTouch(ev)); + this.cleanup(); // call last so that pointerup has access to props + this.isTouchDragging = false; + startIgnoringMouse(); + } + }; + this.handleTouchScroll = () => { + this.wasTouchScroll = true; + }; + this.handleScroll = (ev) => { + if (!this.shouldIgnoreMove) { + let pageX = (window.scrollX - this.prevScrollX) + this.prevPageX; + let pageY = (window.scrollY - this.prevScrollY) + this.prevPageY; + this.emitter.trigger('pointermove', { + origEvent: ev, + isTouch: this.isTouchDragging, + subjectEl: this.subjectEl, + pageX, + pageY, + deltaX: pageX - this.origPageX, + deltaY: pageY - this.origPageY, + }); + } + }; + this.containerEl = containerEl; + this.emitter = new Emitter(); + containerEl.addEventListener('mousedown', this.handleMouseDown); + containerEl.addEventListener('touchstart', this.handleTouchStart, { passive: true }); + listenerCreated(); + } + destroy() { + this.containerEl.removeEventListener('mousedown', this.handleMouseDown); + this.containerEl.removeEventListener('touchstart', this.handleTouchStart, { passive: true }); + listenerDestroyed(); + } + cancel() { + if (this.isDragging) { + this.cleanup(); + } + } + tryStart(ev) { + let subjectEl = this.querySubjectEl(ev); + let downEl = ev.target; + if (subjectEl && + (!this.handleSelector || downEl.closest(this.handleSelector))) { + this.subjectEl = subjectEl; + this.isDragging = true; // do this first so cancelTouchScroll will work + this.wasTouchScroll = false; + return true; + } + return false; + } + cleanup() { + isWindowTouchMoveCancelled = false; + this.isDragging = false; + this.subjectEl = null; + // keep wasTouchScroll around for later access + this.destroyScrollWatch(); + } + querySubjectEl(ev) { + if (this.selector) { + return ev.target.closest(this.selector); + } + return this.containerEl; + } + shouldIgnoreMouse() { + return ignoreMouseDepth || this.isTouchDragging; + } + // can be called by user of this class, to cancel touch-based scrolling for the current drag + cancelTouchScroll() { + if (this.isDragging) { + isWindowTouchMoveCancelled = true; + } + } + // Scrolling that simulates pointermoves + // ---------------------------------------------------------------------------------------------------- + initScrollWatch(ev) { + if (this.shouldWatchScroll) { + this.recordCoords(ev); + window.addEventListener('scroll', this.handleScroll, true); // useCapture=true + } + } + recordCoords(ev) { + if (this.shouldWatchScroll) { + this.prevPageX = ev.pageX; + this.prevPageY = ev.pageY; + this.prevScrollX = window.scrollX; + this.prevScrollY = window.scrollY; + } + } + destroyScrollWatch() { + if (this.shouldWatchScroll) { + window.removeEventListener('scroll', this.handleScroll, true); // useCaptured=true + } + } + // Event Normalization + // ---------------------------------------------------------------------------------------------------- + createEventFromMouse(ev, isFirst) { + let deltaX = 0; + let deltaY = 0; + // TODO: repeat code + if (isFirst) { + this.origPageX = ev.pageX; + this.origPageY = ev.pageY; + } + else { + deltaX = ev.pageX - this.origPageX; + deltaY = ev.pageY - this.origPageY; + } + return { + origEvent: ev, + isTouch: false, + subjectEl: this.subjectEl, + pageX: ev.pageX, + pageY: ev.pageY, + deltaX, + deltaY, + }; + } + createEventFromTouch(ev, isFirst) { + let touches = ev.touches; + let pageX; + let pageY; + let deltaX = 0; + let deltaY = 0; + // if touch coords available, prefer, + // because FF would give bad ev.pageX ev.pageY + if (touches && touches.length) { + pageX = touches[0].pageX; + pageY = touches[0].pageY; + } + else { + pageX = ev.pageX; + pageY = ev.pageY; + } + // TODO: repeat code + if (isFirst) { + this.origPageX = pageX; + this.origPageY = pageY; + } + else { + deltaX = pageX - this.origPageX; + deltaY = pageY - this.origPageY; + } + return { + origEvent: ev, + isTouch: true, + subjectEl: this.subjectEl, + pageX, + pageY, + deltaX, + deltaY, + }; + } + } + // Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) + function isPrimaryMouseButton(ev) { + return ev.button === 0 && !ev.ctrlKey; + } + // Ignoring fake mouse events generated by touch + // ---------------------------------------------------------------------------------------------------- + function startIgnoringMouse() { + ignoreMouseDepth += 1; + setTimeout(() => { + ignoreMouseDepth -= 1; + }, config.touchMouseIgnoreWait); + } + // We want to attach touchmove as early as possible for Safari + // ---------------------------------------------------------------------------------------------------- + function listenerCreated() { + listenerCnt += 1; + if (listenerCnt === 1) { + window.addEventListener('touchmove', onWindowTouchMove, { passive: false }); + } + } + function listenerDestroyed() { + listenerCnt -= 1; + if (!listenerCnt) { + window.removeEventListener('touchmove', onWindowTouchMove, { passive: false }); + } + } + function onWindowTouchMove(ev) { + if (isWindowTouchMoveCancelled) { + ev.preventDefault(); + } + } + + /* + An effect in which an element follows the movement of a pointer across the screen. + The moving element is a clone of some other element. + Must call start + handleMove + stop. + */ + class ElementMirror { + constructor() { + this.isVisible = false; // must be explicitly enabled + this.sourceEl = null; + this.mirrorEl = null; + this.sourceElRect = null; // screen coords relative to viewport + // options that can be set directly by caller + this.parentNode = document.body; // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues + this.zIndex = 9999; + this.revertDuration = 0; + this.colorScheme = ''; + } + start(sourceEl, pageX, pageY) { + this.sourceEl = sourceEl; + this.sourceElRect = this.sourceEl.getBoundingClientRect(); + this.origScreenX = pageX - window.scrollX; + this.origScreenY = pageY - window.scrollY; + this.deltaX = 0; + this.deltaY = 0; + this.updateElPosition(); + } + handleMove(pageX, pageY) { + this.deltaX = (pageX - window.scrollX) - this.origScreenX; + this.deltaY = (pageY - window.scrollY) - this.origScreenY; + this.updateElPosition(); + } + // can be called before start + setIsVisible(bool) { + if (bool) { + if (!this.isVisible) { + if (this.mirrorEl) { + // important because competes with util.module.css classNames, which are all important + // TODO: attach a util className here instead? + this.mirrorEl.style.setProperty('display', '', 'important'); + } + this.isVisible = bool; // needs to happen before updateElPosition + this.updateElPosition(); // because was not updating the position while invisible + } + } + else if (this.isVisible) { + if (this.mirrorEl) { + // important because competes with util.module.css classNames, which are all important + // TODO: attach a util className here instead? + this.mirrorEl.style.setProperty('display', 'none', 'important'); + } + this.isVisible = bool; + } + } + // always async + stop(needsRevertAnimation, callback) { + let done = () => { + this.cleanup(); + callback(); + }; + if (needsRevertAnimation && + this.mirrorEl && + this.isVisible && + this.revertDuration && // if 0, transition won't work + (this.deltaX || this.deltaY) // if same coords, transition won't work + ) { + this.doRevertAnimation(done, this.revertDuration); + } + else { + setTimeout(done, 0); + } + } + doRevertAnimation(callback, revertDuration) { + let mirrorEl = this.mirrorEl; + let finalSourceElRect = this.sourceEl.getBoundingClientRect(); // because autoscrolling might have happened + mirrorEl.style.transition = + 'top ' + revertDuration + 'ms,' + + 'left ' + revertDuration + 'ms'; + applyStyle(mirrorEl, { + left: finalSourceElRect.left, + top: finalSourceElRect.top, + }); + whenTransitionDone(mirrorEl, () => { + mirrorEl.style.transition = ''; + callback(); + }); + } + cleanup() { + if (this.mirrorEl) { + this.mirrorEl.remove(); + this.mirrorEl = null; + } + this.sourceEl = null; + } + updateElPosition() { + if (this.sourceEl && this.isVisible) { + applyStyle(this.getMirrorEl(), { + left: this.sourceElRect.left + this.deltaX, + top: this.sourceElRect.top + this.deltaY, + }); + } + } + getMirrorEl() { + let sourceElRect = this.sourceElRect; + let mirrorEl = this.mirrorEl; + if (!mirrorEl) { + mirrorEl = this.mirrorEl = this.sourceEl.cloneNode(true); // cloneChildren=true + // we don't want long taps or any mouse interaction causing selection/menus. + // would use preventSelection(), but that prevents selectstart, causing problems. + // TODO: make className for this? + mirrorEl.style.userSelect = 'none'; + mirrorEl.style.webkitUserSelect = 'none'; + mirrorEl.style.pointerEvents = 'none'; + if (this.colorScheme) { + mirrorEl.setAttribute('data-color-scheme', this.colorScheme); + } + mirrorEl.classList.add(classNames.borderBoxRoot); + applyStyle(mirrorEl, { + position: 'fixed', + zIndex: this.zIndex, + visibility: '', // in case original element was hidden by the drag effect + width: sourceElRect.right - sourceElRect.left, // explicit height in case there was a 'right' value + height: sourceElRect.bottom - sourceElRect.top, // explicit width in case there was a 'bottom' value + right: 'auto', // erase and set width instead + bottom: 'auto', // erase and set height instead + margin: 0, + }); + this.parentNode.appendChild(mirrorEl); + } + return mirrorEl; + } + } + + /* eslint max-classes-per-file: "off" */ + /* + An object for getting/setting scroll-related information for an element. + Internally, this is done very differently for window versus DOM element, + so this object serves as a common interface. + */ + class ScrollController { + getMaxScrollTop() { + return this.getScrollHeight() - this.getClientHeight(); + } + getMaxScrollLeft() { + return this.getScrollWidth() - this.getClientWidth(); + } + canScrollVertically() { + return this.getMaxScrollTop() > 0; + } + canScrollHorizontally() { + return this.getMaxScrollLeft() > 0; + } + canScrollUp() { + return this.getScrollTop() > 0; + } + canScrollDown() { + return this.getScrollTop() < this.getMaxScrollTop(); + } + canScrollLeft() { + return this.getScrollLeft() > 0; + } + canScrollRight() { + return this.getScrollLeft() < this.getMaxScrollLeft(); + } + } + class ElementScrollController extends ScrollController { + constructor(el) { + super(); + this.el = el; + } + getScrollTop() { + return this.el.scrollTop; + } + getScrollLeft() { + return this.el.scrollLeft; + } + setScrollTop(top) { + this.el.scrollTop = top; + } + setScrollLeft(left) { + this.el.scrollLeft = left; + } + getScrollWidth() { + return this.el.scrollWidth; + } + getScrollHeight() { + return this.el.scrollHeight; + } + getClientHeight() { + return this.el.clientHeight; + } + getClientWidth() { + return this.el.clientWidth; + } + } + class WindowScrollController extends ScrollController { + getScrollTop() { + return window.scrollY; + } + getScrollLeft() { + return window.scrollX; + } + setScrollTop(n) { + window.scroll(window.scrollX, n); + } + setScrollLeft(n) { + window.scroll(n, window.scrollY); + } + getScrollWidth() { + return document.documentElement.scrollWidth; + } + getScrollHeight() { + return document.documentElement.scrollHeight; + } + getClientHeight() { + return document.documentElement.clientHeight; + } + getClientWidth() { + return document.documentElement.clientWidth; + } + } + + /* + Is a cache for a given element's scroll information (all the info that ScrollController stores) + in addition the "client rectangle" of the element.. the area within the scrollbars. + + The cache can be in one of two modes: + - doesListening:false - ignores when the container is scrolled by someone else + - doesListening:true - watch for scrolling and update the cache + */ + class ScrollGeomCache extends ScrollController { + constructor(scrollController, doesListening) { + super(); + this.handleScroll = () => { + this.scrollTop = this.scrollController.getScrollTop(); + this.scrollLeft = this.scrollController.getScrollLeft(); + this.handleScrollChange(); + }; + this.scrollController = scrollController; + this.doesListening = doesListening; + this.scrollTop = this.origScrollTop = scrollController.getScrollTop(); + this.scrollLeft = this.origScrollLeft = scrollController.getScrollLeft(); + this.scrollWidth = scrollController.getScrollWidth(); + this.scrollHeight = scrollController.getScrollHeight(); + this.clientWidth = scrollController.getClientWidth(); + this.clientHeight = scrollController.getClientHeight(); + this.clientRect = this.computeClientRect(); // do last in case it needs cached values + if (this.doesListening) { + this.getEventTarget().addEventListener('scroll', this.handleScroll); + } + } + destroy() { + if (this.doesListening) { + this.getEventTarget().removeEventListener('scroll', this.handleScroll); + } + } + getScrollTop() { + return this.scrollTop; + } + getScrollLeft() { + return this.scrollLeft; + } + setScrollTop(top) { + this.scrollController.setScrollTop(top); + if (!this.doesListening) { + // we are not relying on the element to normalize out-of-bounds scroll values + // so we need to sanitize ourselves + this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0); + this.handleScrollChange(); + } + } + setScrollLeft(top) { + this.scrollController.setScrollLeft(top); + if (!this.doesListening) { + // we are not relying on the element to normalize out-of-bounds scroll values + // so we need to sanitize ourselves + this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0); + this.handleScrollChange(); + } + } + getClientWidth() { + return this.clientWidth; + } + getClientHeight() { + return this.clientHeight; + } + getScrollWidth() { + return this.scrollWidth; + } + getScrollHeight() { + return this.scrollHeight; + } + handleScrollChange() { + } + } + + class ElementScrollGeomCache extends ScrollGeomCache { + constructor(el, doesListening) { + super(new ElementScrollController(el), doesListening); + } + getEventTarget() { + return this.scrollController.el; + } + computeClientRect() { + return computeInnerRect(this.scrollController.el); + } + } + + class WindowScrollGeomCache extends ScrollGeomCache { + constructor(doesListening) { + super(new WindowScrollController(), doesListening); + } + getEventTarget() { + return window; + } + computeClientRect() { + return { + left: this.scrollLeft, + right: this.scrollLeft + this.clientWidth, + top: this.scrollTop, + bottom: this.scrollTop + this.clientHeight, + }; + } + // the window is the only scroll object that changes it's rectangle relative + // to the document's topleft as it scrolls + handleScrollChange() { + this.clientRect = this.computeClientRect(); + } + } + + // If available we are using native "performance" API instead of "Date" + // Read more about it on MDN: + // https://developer.mozilla.org/en-US/docs/Web/API/Performance + const getTime = typeof performance === 'function' ? performance.now : Date.now; + /* + For a pointer interaction, automatically scrolls certain scroll containers when the pointer + approaches the edge. + + The caller must call start + handleMove + stop. + */ + class AutoScroller { + constructor() { + // options that can be set by caller + this.isEnabled = true; + this.scrollQuery = [window, `.${classNames.internalScroller}`]; + this.edgeThreshold = 50; // pixels + this.maxVelocity = 300; // pixels per second + // internal state + this.pointerScreenX = null; + this.pointerScreenY = null; + this.isAnimating = false; + this.scrollCaches = null; + // protect against the initial pointerdown being too close to an edge and starting the scroll + this.everMovedUp = false; + this.everMovedDown = false; + this.everMovedLeft = false; + this.everMovedRight = false; + this.animate = () => { + if (this.isAnimating) { // wasn't cancelled between animation calls + let edge = this.computeBestEdge(this.pointerScreenX + window.scrollX, this.pointerScreenY + window.scrollY); + if (edge) { + let now = getTime(); + this.handleSide(edge, (now - this.msSinceRequest) / 1000); + this.requestAnimation(now); + } + else { + this.isAnimating = false; // will stop animation + } + } + }; + } + start(pageX, pageY, scrollStartEl) { + if (this.isEnabled) { + this.scrollCaches = this.buildCaches(scrollStartEl); + this.pointerScreenX = null; + this.pointerScreenY = null; + this.everMovedUp = false; + this.everMovedDown = false; + this.everMovedLeft = false; + this.everMovedRight = false; + this.handleMove(pageX, pageY); + } + } + handleMove(pageX, pageY) { + if (this.isEnabled) { + let pointerScreenX = pageX - window.scrollX; + let pointerScreenY = pageY - window.scrollY; + let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY; + let xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX; + if (yDelta < 0) { + this.everMovedUp = true; + } + else if (yDelta > 0) { + this.everMovedDown = true; + } + if (xDelta < 0) { + this.everMovedLeft = true; + } + else if (xDelta > 0) { + this.everMovedRight = true; + } + this.pointerScreenX = pointerScreenX; + this.pointerScreenY = pointerScreenY; + if (!this.isAnimating) { + this.isAnimating = true; + this.requestAnimation(getTime()); + } + } + } + stop() { + if (this.isEnabled) { + this.isAnimating = false; // will stop animation + for (let scrollCache of this.scrollCaches) { + scrollCache.destroy(); + } + this.scrollCaches = null; + } + } + requestAnimation(now) { + this.msSinceRequest = now; + requestAnimationFrame(this.animate); + } + handleSide(edge, seconds) { + let { scrollCache } = edge; + let { edgeThreshold } = this; + let invDistance = edgeThreshold - edge.distance; + let velocity = // the closer to the edge, the faster we scroll + ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic + this.maxVelocity * seconds; + let sign = 1; + switch (edge.name) { + case 'left': + sign = -1; + // falls through + case 'right': + scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign); + break; + case 'top': + sign = -1; + // falls through + case 'bottom': + scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign); + break; + } + } + // left/top are relative to document topleft + computeBestEdge(left, top) { + let { edgeThreshold } = this; + let bestSide = null; + let scrollCaches = this.scrollCaches || []; + for (let scrollCache of scrollCaches) { + let rect = scrollCache.clientRect; + let leftDist = left - rect.left; + let rightDist = rect.right - left; + let topDist = top - rect.top; + let bottomDist = rect.bottom - top; + // completely within the rect? + if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) { + if (topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() && + (!bestSide || bestSide.distance > topDist)) { + bestSide = { scrollCache, name: 'top', distance: topDist }; + } + if (bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && + (!bestSide || bestSide.distance > bottomDist)) { + bestSide = { scrollCache, name: 'bottom', distance: bottomDist }; + } + /* + TODO: fix broken RTL scrolling. canScrollLeft always returning false + https://github.com/fullcalendar/fullcalendar/issues/4837 + */ + if (leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && + (!bestSide || bestSide.distance > leftDist)) { + bestSide = { scrollCache, name: 'left', distance: leftDist }; + } + if (rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && + (!bestSide || bestSide.distance > rightDist)) { + bestSide = { scrollCache, name: 'right', distance: rightDist }; + } + } + } + return bestSide; + } + buildCaches(scrollStartEl) { + return this.queryScrollEls(scrollStartEl).map((el) => { + if (el === window) { + return new WindowScrollGeomCache(false); // false = don't listen to user-generated scrolls + } + return new ElementScrollGeomCache(el, false); // false = don't listen to user-generated scrolls + }); + } + queryScrollEls(scrollStartEl) { + let els = []; + for (let query of this.scrollQuery) { + if (typeof query === 'object') { + els.push(query); + } + else { + /* + TODO: in the future, always have auto-scroll happen on element where current Hit came from + Ticket: https://github.com/fullcalendar/fullcalendar/issues/4593 + */ + els.push(...Array.prototype.slice.call(scrollStartEl.getRootNode().querySelectorAll(query))); + } + } + return els; + } + } + + /* + Monitors dragging on an element. Has a number of high-level features: + - minimum distance required before dragging + - minimum wait time ("delay") before dragging + - a mirror element that follows the pointer + */ + class FeaturefulElementDragging extends ElementDragging { + constructor(containerEl, selector) { + super(containerEl); + this.containerEl = containerEl; + // options that can be directly set by caller + // the caller can also set the PointerDragging's options as well + this.delay = null; + this.minDistance = 0; + this.touchScrollAllowed = true; // prevents drag from starting and blocks scrolling during drag + this.mirrorNeedsRevert = false; + this.isInteracting = false; // is the user validly moving the pointer? lasts until pointerup + this.isDragging = false; // is it INTENTFULLY dragging? lasts until after revert animation + this.isDelayEnded = false; + this.isDistanceSurpassed = false; + this.delayTimeoutId = null; + this.onPointerDown = (ev) => { + if (!this.isDragging) { // so new drag doesn't happen while revert animation is going + this.isInteracting = true; + this.isDelayEnded = false; + this.isDistanceSurpassed = false; + this.emitter.trigger('pointerdown', ev); + if (this.isInteracting) { // not cancelled? + preventSelection(document.body); + preventContextMenu(document.body); + // prevent links from being visited if there's an eventual drag. + // also prevents selection in older browsers (maybe?). + // not necessary for touch, besides, browser would complain about passiveness. + if (!ev.isTouch) { + ev.origEvent.preventDefault(); + } + // actions related to initiating dragstart+dragmove+dragend... + this.mirror.setIsVisible(false); // reset. caller must set-visible + this.mirror.start(ev.subjectEl, ev.pageX, ev.pageY); // must happen on first pointer down + this.startDelay(ev); + if (!this.minDistance) { + this.handleDistanceSurpassed(ev); + } + } + } + }; + this.onPointerMove = (ev) => { + if (this.isInteracting) { + this.emitter.trigger('pointermove', ev); + if (!this.isDistanceSurpassed) { + let minDistance = this.minDistance; + let distanceSq; // current distance from the origin, squared + let { deltaX, deltaY } = ev; + distanceSq = deltaX * deltaX + deltaY * deltaY; + if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem + this.handleDistanceSurpassed(ev); + } + } + if (this.isDragging) { + // a real pointer move? (not one simulated by scrolling) + if (ev.origEvent.type !== 'scroll') { + this.mirror.handleMove(ev.pageX, ev.pageY); + this.autoScroller.handleMove(ev.pageX, ev.pageY); + } + this.emitter.trigger('dragmove', ev); + } + } + }; + this.onPointerUp = (ev) => { + if (this.isInteracting) { + this.isInteracting = false; + allowSelection(document.body); + allowContextMenu(document.body); + this.emitter.trigger('pointerup', ev); // can potentially set mirrorNeedsRevert + if (this.isDragging) { + this.autoScroller.stop(); + this.tryStopDrag(ev); // which will stop the mirror + } + if (this.delayTimeoutId) { + clearTimeout(this.delayTimeoutId); + this.delayTimeoutId = null; + } + } + }; + let pointer = this.pointer = new PointerDragging(containerEl); + pointer.emitter.on('pointerdown', this.onPointerDown); + pointer.emitter.on('pointermove', this.onPointerMove); + pointer.emitter.on('pointerup', this.onPointerUp); + if (selector) { + pointer.selector = selector; + } + this.mirror = new ElementMirror(); + this.autoScroller = new AutoScroller(); + } + destroy() { + this.pointer.destroy(); + // HACK: simulate a pointer-up to end the current drag + // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire) + this.onPointerUp({}); + } + startDelay(ev) { + if (typeof this.delay === 'number') { + this.delayTimeoutId = setTimeout(() => { + this.delayTimeoutId = null; + this.handleDelayEnd(ev); + }, this.delay); // not assignable to number! + } + else { + this.handleDelayEnd(ev); + } + } + handleDelayEnd(ev) { + this.isDelayEnded = true; + this.tryStartDrag(ev); + } + handleDistanceSurpassed(ev) { + this.isDistanceSurpassed = true; + this.tryStartDrag(ev); + } + tryStartDrag(ev) { + if (this.isDelayEnded && this.isDistanceSurpassed) { + if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) { + this.isDragging = true; + this.mirrorNeedsRevert = false; + this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl); + this.emitter.trigger('dragstart', ev); + if (this.touchScrollAllowed === false) { + this.pointer.cancelTouchScroll(); + } + } + } + } + tryStopDrag(ev) { + // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events + // that come from the document to fire beforehand. much more convenient this way. + this.mirror.stop(this.mirrorNeedsRevert, this.stopDrag.bind(this, ev)); + } + stopDrag(ev) { + this.isDragging = false; + this.emitter.trigger('dragend', ev); + } + // fill in the implementations... + /* + Can only be called by pointerdown to prevent drag + */ + cancel() { + if (this.isInteracting) { + this.isInteracting = false; + this.pointer.cancel(); + } + } + setMirrorIsVisible(bool) { + this.mirror.setIsVisible(bool); + } + setMirrorNeedsRevert(bool) { + this.mirrorNeedsRevert = bool; + } + setAutoScrollEnabled(bool) { + this.autoScroller.isEnabled = bool; + } + } + + /* + When this class is instantiated, it records the offset of an element (relative to the document topleft), + and continues to monitor scrolling, updating the cached coordinates if it needs to. + Does not access the DOM after instantiation, so highly performant. + + Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element + and an determine if a given point is inside the combined clipping rectangle. + */ + class OffsetTracker { + constructor(el) { + this.el = el; + this.origRect = computeRect(el); + this.isRtl = computeElIsRtl(el); + // will work fine for divs that have overflow:hidden + this.scrollCaches = getClippingParents(el).map((scrollEl) => new ElementScrollGeomCache(scrollEl, true)); + } + destroy() { + for (let scrollCache of this.scrollCaches) { + scrollCache.destroy(); + } + } + computeLeft() { + let left = this.origRect.left; + for (let scrollCache of this.scrollCaches) { + left += scrollCache.origScrollLeft - scrollCache.getScrollLeft(); + } + return left; + } + computeTop() { + let top = this.origRect.top; + for (let scrollCache of this.scrollCaches) { + top += scrollCache.origScrollTop - scrollCache.getScrollTop(); + } + return top; + } + isWithinClipping(pageX, pageY) { + let point = { left: pageX, top: pageY }; + for (let scrollCache of this.scrollCaches) { + if (!isIgnoredClipping(scrollCache.getEventTarget()) && + !pointInsideRect(point, scrollCache.clientRect)) { + return false; + } + } + return true; + } + } + // certain clipping containers should never constrain interactions, like and + // https://github.com/fullcalendar/fullcalendar/issues/3615 + function isIgnoredClipping(node) { + let tagName = node.tagName; + return tagName === 'HTML' || tagName === 'BODY'; + } + + /* + Tracks movement over multiple droppable areas (aka "hits") + that exist in one or more DateComponents. + Relies on an existing draggable. + + emits: + - pointerdown + - dragstart + - hitchange - fires initially, even if not over a hit + - pointerup + - (hitchange - again, to null, if ended over a hit) + - dragend + */ + class HitDragging { + constructor(dragging, droppableStore) { + // options that can be set by caller + this.useSubjectCenter = false; + this.requireInitial = true; // if doesn't start out on a hit, won't emit any events + this.disablePointCheck = false; + this.initialHit = null; + this.movingHit = null; + this.finalHit = null; // won't ever be populated if shouldIgnoreMove + this.handlePointerDown = (ev) => { + let { dragging } = this; + this.initialHit = null; + this.movingHit = null; + this.finalHit = null; + this.prepareHits(); + this.processFirstCoord(ev); + if (this.initialHit || !this.requireInitial) { + // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :( + this.emitter.trigger('pointerdown', ev); + } + else { + dragging.cancel(); + } + }; + this.handleDragStart = (ev) => { + this.emitter.trigger('dragstart', ev); + this.handleMove(ev, true); // force = fire even if initially null + }; + this.handleDragMove = (ev) => { + this.emitter.trigger('dragmove', ev); + this.handleMove(ev); + }; + this.handlePointerUp = (ev) => { + this.releaseHits(); + this.emitter.trigger('pointerup', ev); + }; + this.handleDragEnd = (ev) => { + if (this.movingHit) { + this.emitter.trigger('hitupdate', null, true, ev); + } + this.finalHit = this.movingHit; + this.movingHit = null; + this.emitter.trigger('dragend', ev); + }; + this.droppableStore = droppableStore; + dragging.emitter.on('pointerdown', this.handlePointerDown); + dragging.emitter.on('dragstart', this.handleDragStart); + dragging.emitter.on('dragmove', this.handleDragMove); + dragging.emitter.on('pointerup', this.handlePointerUp); + dragging.emitter.on('dragend', this.handleDragEnd); + this.dragging = dragging; + this.emitter = new Emitter(); + } + // sets initialHit + // sets coordAdjust + processFirstCoord(ev) { + let origPoint = { left: ev.pageX, top: ev.pageY }; + let adjustedPoint = origPoint; + let subjectEl = ev.subjectEl; + let subjectRect; + if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot + subjectRect = computeRect(subjectEl); + adjustedPoint = constrainPoint(adjustedPoint, subjectRect); + } + let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top); + if (initialHit) { + if (this.useSubjectCenter && subjectRect) { + let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect); + if (slicedSubjectRect) { + adjustedPoint = getRectCenter(slicedSubjectRect); + } + } + this.coordAdjust = diffPoints(adjustedPoint, origPoint); + } + else { + this.coordAdjust = { left: 0, top: 0 }; + } + } + handleMove(ev, forceHandle) { + let hit = this.queryHitForOffset(ev.pageX + this.coordAdjust.left, ev.pageY + this.coordAdjust.top); + if (forceHandle || !isHitsEqual(this.movingHit, hit)) { + this.movingHit = hit; + this.emitter.trigger('hitupdate', hit, false, ev); + } + } + prepareHits() { + this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => { + interactionSettings.component.prepareHits(); + return new OffsetTracker(interactionSettings.el); + }); + } + releaseHits() { + let { offsetTrackers } = this; + for (let id in offsetTrackers) { + offsetTrackers[id].destroy(); + } + this.offsetTrackers = {}; + } + queryHitForOffset(offsetLeft, offsetTop) { + let { droppableStore, offsetTrackers } = this; + let bestHit = null; + for (let id in droppableStore) { + let component = droppableStore[id].component; + let offsetTracker = offsetTrackers[id]; + if (offsetTracker && // wasn't destroyed mid-drag + offsetTracker.isWithinClipping(offsetLeft, offsetTop)) { + let originLeft = offsetTracker.computeLeft(); + let originTop = offsetTracker.computeTop(); + let positionLeft = offsetLeft - originLeft; + let positionTop = offsetTop - originTop; + let { origRect } = offsetTracker; + let width = origRect.right - origRect.left; + let height = origRect.bottom - origRect.top; + if ( + // must be within the element's bounds + positionLeft >= 0 && positionLeft < width && + positionTop >= 0 && positionTop < height) { + let hit = component.queryHit(offsetTracker.isRtl, positionLeft, positionTop, width, height); + if (hit && ( + // make sure the hit is within activeRange, meaning it's not a dead cell + rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range)) && + // Ensure the component we are querying for the hit is accessibly my the pointer + // Prevents obscured calendars (ex: under a modal dialog) from accepting hit + // https://github.com/fullcalendar/fullcalendar/issues/5026 + (this.disablePointCheck || + offsetTracker.el.contains(offsetTracker.el.getRootNode().elementFromPoint( + // add-back origins to get coordinate relative to top-left of window viewport + positionLeft + originLeft - window.scrollX, positionTop + originTop - window.scrollY))) && + (!bestHit || hit.layer > bestHit.layer)) { + hit.componentId = id; + hit.context = component.context; + // TODO: better way to re-orient rectangle + hit.rect.left += originLeft; + hit.rect.right += originLeft; + hit.rect.top += originTop; + hit.rect.bottom += originTop; + bestHit = hit; + } + } + } + } + return bestHit; + } + } + function isHitsEqual(hit0, hit1) { + if (!hit0 && !hit1) { + return true; + } + if (Boolean(hit0) !== Boolean(hit1)) { + return false; + } + return isDateSpansEqual(hit0.dateSpan, hit1.dateSpan); + } + + function buildDatePointApiWithContext(dateSpan, context) { + let props = {}; + for (let transform of context.pluginHooks.datePointTransforms) { + Object.assign(props, transform(dateSpan, context)); + } + Object.assign(props, buildDatePointApi(dateSpan, context.dateEnv)); + return props; + } + function buildDatePointApi(span, dateEnv) { + return { + date: dateEnv.toDate(span.range.start), + dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }), + allDay: span.allDay, + }; + } + + /* + Monitors when the user clicks on a specific date/time of a component. + A pointerdown+pointerup on the same "hit" constitutes a click. + */ + class DateClicking extends Interaction { + constructor(settings) { + super(settings); + this.handlePointerDown = (pev) => { + let { dragging } = this; + let downEl = pev.origEvent.target; + /* + If no dateClick, allow text on dates to be text-selectable + */ + const canDateClick = this.component.context.emitter.hasHandlers('dateClick') && + this.component.isValidDateDownEl(downEl); + if (!canDateClick) { + dragging.cancel(); + } + }; + // won't even fire if moving was ignored + this.handleDragEnd = (ev) => { + let { component } = this; + let { pointer } = this.dragging; + if (!pointer.wasTouchScroll) { + let { initialHit, finalHit } = this.hitDragging; + if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { + let { context } = component; + let data = { + ...buildDatePointApiWithContext(initialHit.dateSpan, context), + dayEl: initialHit.getDayEl(), + jsEvent: ev.origEvent, + view: context.viewApi || context.calendarApi.view, + }; + context.emitter.trigger('dateClick', data); + } + } + }; + // we DO want to watch pointer moves because otherwise finalHit won't get populated + this.dragging = new FeaturefulElementDragging(settings.el); + this.dragging.autoScroller.isEnabled = false; + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', this.handlePointerDown); + hitDragging.emitter.on('dragend', this.handleDragEnd); + } + destroy() { + this.dragging.destroy(); + } + } + + /* + Tracks when the user selects a portion of time of a component, + constituted by a drag over date cells, with a possible delay at the beginning of the drag. + */ + class DateSelecting extends Interaction { + constructor(settings) { + super(settings); + this.dragSelection = null; + this.handlePointerDown = (ev) => { + let { component, dragging } = this; + let { options } = component.context; + let canDateSelect = options.selectable && + component.isValidDateDownEl(ev.origEvent.target); + if (!canDateSelect) { + dragging.cancel(); + } + else { + // if touch, require user to hold down + dragging.delay = ev.isTouch ? getComponentTouchDelay$1(component) : null; + } + }; + this.handleDragStart = (ev) => { + this.component.context.calendarApi.unselect(ev); // unselect previous selections + }; + this.handleHitUpdate = (hit, isFinal) => { + let { context } = this.component; + let dragSelection = null; + let isInvalid = false; + if (hit) { + let initialHit = this.hitDragging.initialHit; + let disallowed = hit.componentId === initialHit.componentId + && this.isHitComboAllowed + && !this.isHitComboAllowed(initialHit, hit); + if (!disallowed) { + dragSelection = joinHitsIntoSelection(initialHit, hit, context.pluginHooks.dateSelectionTransformers); + } + if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) { + isInvalid = true; + dragSelection = null; + } + } + if (dragSelection) { + context.dispatch({ type: 'SELECT_DATES', selection: dragSelection }); + } + else if (!isFinal) { // only unselect if moved away while dragging + context.dispatch({ type: 'UNSELECT_DATES' }); + } + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + this.dragSelection = dragSelection; // only clear if moved away from all hits while dragging + } + }; + this.handlePointerUp = (pev) => { + if (this.dragSelection) { + // selection is already rendered, so just need to report selection + triggerDateSelect(this.dragSelection, pev, this.component.context); + this.dragSelection = null; + } + else { + this.component.context.emitter.trigger('_noDateSelect'); + } + }; + let { component } = settings; + let { options } = component.context; + let dragging = this.dragging = new FeaturefulElementDragging(settings.el); + dragging.touchScrollAllowed = false; + dragging.minDistance = options.selectMinDistance || 0; + dragging.autoScroller.isEnabled = options.dragScroll; + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', this.handlePointerDown); + hitDragging.emitter.on('dragstart', this.handleDragStart); + hitDragging.emitter.on('hitupdate', this.handleHitUpdate); + hitDragging.emitter.on('pointerup', this.handlePointerUp); + } + destroy() { + this.dragging.destroy(); + } + } + function getComponentTouchDelay$1(component) { + let { options } = component.context; + let delay = options.selectLongPressDelay; + if (delay == null) { + delay = options.longPressDelay; + } + return delay; + } + function joinHitsIntoSelection(hit0, hit1, dateSelectionTransformers) { + let dateSpan0 = hit0.dateSpan; + let dateSpan1 = hit1.dateSpan; + let ms = [ + dateSpan0.range.start, + dateSpan0.range.end, + dateSpan1.range.start, + dateSpan1.range.end, + ]; + ms.sort(compareNumbers); + let props = {}; + for (let transformer of dateSelectionTransformers) { + let res = transformer(hit0, hit1); + if (res === false) { + return null; + } + if (res) { + Object.assign(props, res); + } + } + props.range = { start: ms[0], end: ms[3] }; + props.allDay = dateSpan0.allDay; + return props; + } + + class EventDragging extends Interaction { + constructor(settings) { + super(settings); + // internal state + this.subjectEl = null; + this.isDragging = false; + this.eventRange = null; + this.relevantEvents = null; // the events being dragged + this.receivingContext = null; + this.validMutation = null; + this.mutatedRelevantEvents = null; + this.handlePointerDown = (ev) => { + let origTarget = ev.origEvent.target; + let { component, dragging } = this; + let { mirror } = dragging; + let { options } = component.context; + let initialContext = component.context; + this.subjectEl = ev.subjectEl; + let eventRange = this.eventRange = getElEventRange(ev.subjectEl); + let eventInstanceId = eventRange.instance.instanceId; + this.relevantEvents = getRelevantEvents(initialContext.getCurrentData().eventStore, eventInstanceId); + dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance; + dragging.delay = + // only do a touch delay if touch and this event hasn't been selected yet + (ev.isTouch && eventInstanceId !== component.props.eventSelection) ? + getComponentTouchDelay(component) : + null; + mirror.parentNode = getAppendableRoot(origTarget); + mirror.revertDuration = options.dragRevertDuration; + mirror.colorScheme = options.colorScheme || ''; + let isValid = component.isValidSegDownEl(origTarget) && + !origTarget.closest(`.${classNames.internalEventResizer}`); // NOT on a resizer + if (!isValid) { + dragging.cancel(); + } + else { + // disable dragging for elements that are resizable (ie, selectable) + // but are not draggable + // TODO: merge this with .cancel() ? + this.isDragging = ev.subjectEl + .classList.contains(classNames.internalEventDraggable); + } + }; + this.handleDragStart = (ev) => { + let initialContext = this.component.context; + let eventRange = this.eventRange; + let eventInstanceId = eventRange.instance.instanceId; + if (ev.isTouch) { + // need to select a different event? + if (eventInstanceId !== this.component.props.eventSelection) { + initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId }); + } + } + else { + // if now using mouse, but was previous touch interaction, clear selected event + initialContext.dispatch({ type: 'UNSELECT_EVENT' }); + } + if (this.isDragging) { + initialContext.calendarApi.unselect(ev); // unselect *date* selection + initialContext.emitter.trigger('eventDragStart', { + el: this.subjectEl, + event: new EventImpl(initialContext, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent, // Is this always a mouse event? See #4655 + view: initialContext.viewApi, + }); + } + }; + this.handleHitUpdate = (hit, isFinal) => { + if (!this.isDragging) { + return; + } + let relevantEvents = this.relevantEvents; + let initialHit = this.hitDragging.initialHit; + let initialContext = this.component.context; + // states based on new hit + let receivingContext = null; + let mutation = null; + let mutatedRelevantEvents = null; + let isInvalid = false; + let interaction = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + }; + if (hit) { + receivingContext = hit.context; + let receivingOptions = receivingContext.options; + if (initialContext === receivingContext || + (receivingOptions.editable && receivingOptions.droppable)) { + mutation = computeEventMutation(initialHit, hit, this.eventRange.instance.range.start, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers); + if (mutation) { + mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingContext.getCurrentData().eventUiBases, mutation, receivingContext); + interaction.mutatedEvents = mutatedRelevantEvents; + if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) { + isInvalid = true; + mutation = null; + mutatedRelevantEvents = null; + interaction.mutatedEvents = createEmptyEventStore(); + } + } + } + else { + receivingContext = null; + } + } + this.displayDrag(receivingContext, interaction); + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + if (initialContext === receivingContext && // TODO: write test for this + isHitsEqual(initialHit, hit)) { + mutation = null; + } + this.dragging.setMirrorNeedsRevert(!mutation); + // render the mirror if no already-rendered mirror + // TODO: wish we could somehow wait for dispatch to guarantee render + this.dragging.setMirrorIsVisible(!hit || !this.subjectEl.getRootNode().querySelector(`.${classNames.internalEventMirror}`)); + // assign states based on new hit + this.receivingContext = receivingContext; + this.validMutation = mutation; + this.mutatedRelevantEvents = mutatedRelevantEvents; + } + }; + this.handlePointerUp = () => { + if (!this.isDragging) { + this.cleanup(); // because handleDragEnd won't fire + } + }; + this.handleDragEnd = (ev) => { + if (this.isDragging) { + let initialContext = this.component.context; + let initialView = initialContext.viewApi; + let { receivingContext, validMutation } = this; + let eventDef = this.eventRange.def; + let eventInstance = this.eventRange.instance; + let eventApi = new EventImpl(initialContext, eventDef, eventInstance); + let relevantEvents = this.relevantEvents; + let mutatedRelevantEvents = this.mutatedRelevantEvents; + let { finalHit } = this.hitDragging; + this.clearDrag(); // must happen after revert animation + initialContext.emitter.trigger('eventDragStop', { + el: this.subjectEl, + event: eventApi, + jsEvent: ev.origEvent, // Is this always a mouse event? See #4655 + view: initialView, + }); + if (validMutation) { + // dropped within same calendar + if (receivingContext === initialContext) { + let updatedEventApi = new EventImpl(initialContext, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null); + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }); + let eventChangeData = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, initialContext, eventInstance), + revert() { + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change data + }); + }, + }; + let transformed = {}; + for (let transformer of initialContext.getCurrentData().pluginHooks.eventDropTransformers) { + Object.assign(transformed, transformer(validMutation, initialContext)); + } + initialContext.emitter.trigger('eventDrop', { + ...eventChangeData, + ...transformed, + el: ev.subjectEl, + delta: validMutation.datesDelta, + jsEvent: ev.origEvent, // bad + view: initialView, + }); + initialContext.emitter.trigger('eventChange', eventChangeData); + // dropped in different calendar + } + else if (receivingContext) { + let eventRemoveData = { + event: eventApi, + relatedEvents: buildEventApis(relevantEvents, initialContext, eventInstance), + revert() { + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, + }); + }, + }; + initialContext.emitter.trigger('eventLeave', { + ...eventRemoveData, + draggedEl: ev.subjectEl, + view: initialView, + }); + initialContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: relevantEvents, + }); + initialContext.emitter.trigger('eventRemove', eventRemoveData); + let addedEventDef = mutatedRelevantEvents.defs[eventDef.defId]; + let addedEventInstance = mutatedRelevantEvents.instances[eventInstance.instanceId]; + let addedEventApi = new EventImpl(receivingContext, addedEventDef, addedEventInstance); + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }); + let eventAddData = { + event: addedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, receivingContext, addedEventInstance), + revert() { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: mutatedRelevantEvents, + }); + }, + }; + receivingContext.emitter.trigger('eventAdd', eventAddData); + if (ev.isTouch) { + receivingContext.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: eventInstance.instanceId, + }); + } + receivingContext.emitter.trigger('drop', { + ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), + draggedEl: ev.subjectEl, + jsEvent: ev.origEvent, // Is this always a mouse event? See #4655 + view: finalHit.context.viewApi, + }); + receivingContext.emitter.trigger('eventReceive', { + ...eventAddData, + draggedEl: ev.subjectEl, + view: finalHit.context.viewApi, + }); + } + } + else { + initialContext.emitter.trigger('_noEventDrop'); + } + } + this.cleanup(); + }; + let { component } = this; + let { options } = component.context; + let dragging = this.dragging = new FeaturefulElementDragging(settings.el); + dragging.pointer.selector = EventDragging.SELECTOR; + dragging.touchScrollAllowed = false; + dragging.autoScroller.isEnabled = options.dragScroll; + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsStore); + hitDragging.useSubjectCenter = settings.useEventCenter; + hitDragging.emitter.on('pointerdown', this.handlePointerDown); + hitDragging.emitter.on('dragstart', this.handleDragStart); + hitDragging.emitter.on('hitupdate', this.handleHitUpdate); + hitDragging.emitter.on('pointerup', this.handlePointerUp); + hitDragging.emitter.on('dragend', this.handleDragEnd); + } + destroy() { + this.dragging.destroy(); + } + // render a drag state on the next receivingCalendar + displayDrag(nextContext, state) { + let initialContext = this.component.context; + let prevContext = this.receivingContext; + // does the previous calendar need to be cleared? + if (prevContext && prevContext !== nextContext) { + // does the initial calendar need to be cleared? + // if so, don't clear all the way. we still need to to hide the affectedEvents + if (prevContext === initialContext) { + prevContext.dispatch({ + type: 'SET_EVENT_DRAG', + state: { + affectedEvents: state.affectedEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + }, + }); + // completely clear the old calendar if it wasn't the initial + } + else { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + } + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }); + } + } + clearDrag() { + let initialCalendar = this.component.context; + let { receivingContext } = this; + if (receivingContext) { + receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + // the initial calendar might have an dummy drag state from displayDrag + if (initialCalendar !== receivingContext) { + initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + } + cleanup() { + this.isDragging = false; + this.eventRange = null; + this.relevantEvents = null; + this.receivingContext = null; + this.validMutation = null; + this.mutatedRelevantEvents = null; + } + } + // TODO: test this in IE11 + // QUESTION: why do we need it on the resizable??? + EventDragging.SELECTOR = `.${classNames.internalEventDraggable}, .${classNames.internalEventResizable}`; + function computeEventMutation(hit0, hit1, eventInstanceStart, massagers) { + let dateSpan0 = hit0.dateSpan; + let dateSpan1 = hit1.dateSpan; + let date0 = dateSpan0.range.start; + let date1 = dateSpan1.range.start; + let standardProps = {}; + if (dateSpan0.allDay !== dateSpan1.allDay) { + standardProps.allDay = dateSpan1.allDay; + standardProps.hasEnd = hit1.context.options.allDayMaintainDuration; + if (dateSpan1.allDay) { + // means date1 is already start-of-day, + // but date0 needs to be converted + date0 = startOfDay(eventInstanceStart); + } + else { + // Moving from allDate->timed + // Doesn't matter where on the event the drag began, mutate the event's start-date to date1 + date0 = eventInstanceStart; + } + } + let delta = diffDates(date0, date1, hit0.context.dateEnv, hit0.componentId === hit1.componentId ? + hit0.largeUnit : + null); + if (delta.milliseconds) { // has hours/minutes/seconds + standardProps.allDay = false; + } + let mutation = { + datesDelta: delta, + standardProps, + }; + for (let massager of massagers) { + massager(mutation, hit0, hit1); + } + return mutation; + } + function getComponentTouchDelay(component) { + let { options } = component.context; + let delay = options.eventLongPressDelay; + if (delay == null) { + delay = options.longPressDelay; + } + return delay; + } + + class EventResizing extends Interaction { + constructor(settings) { + super(settings); + // internal state + this.draggingSegEl = null; + this.draggingEventRange = null; // TODO: rename to resizingSeg? subjectSeg? + this.eventRange = null; + this.relevantEvents = null; + this.validMutation = null; + this.mutatedRelevantEvents = null; + this.handlePointerDown = (ev) => { + let { component } = this; + let segEl = this.querySegEl(ev); + let eventRange = this.eventRange = getElEventRange(segEl); + this.dragging.minDistance = component.context.options.eventDragMinDistance; + const isValid = this.component.isValidSegDownEl(ev.origEvent.target) && + !(ev.isTouch && this.component.props.eventSelection !== eventRange.instance.instanceId); + if (!isValid) { + this.dragging.cancel(); + } + }; + this.handleDragStart = (ev) => { + let { context } = this.component; + let eventRange = this.eventRange; + this.relevantEvents = getRelevantEvents(context.getCurrentData().eventStore, this.eventRange.instance.instanceId); + let segEl = this.querySegEl(ev); + this.draggingSegEl = segEl; + this.draggingEventRange = getElEventRange(segEl); + context.calendarApi.unselect(); + context.emitter.trigger('eventResizeStart', { + el: segEl, + event: new EventImpl(context, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent, // Is this always a mouse event? See #4655 + view: context.viewApi, + }); + }; + this.handleHitUpdate = (hit, isFinal, ev) => { + let { context } = this.component; + let relevantEvents = this.relevantEvents; + let initialHit = this.hitDragging.initialHit; + let eventInstance = this.eventRange.instance; + let mutation = null; + let mutatedRelevantEvents = null; + let isInvalid = false; + let interaction = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + }; + if (hit) { + let disallowed = hit.componentId === initialHit.componentId + && this.isHitComboAllowed + && !this.isHitComboAllowed(initialHit, hit); + if (!disallowed) { + mutation = computeMutation(initialHit, hit, ev.subjectEl.classList.contains(classNames.internalEventResizerStart), eventInstance.range); + } + } + if (mutation) { + mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context); + interaction.mutatedEvents = mutatedRelevantEvents; + if (!isInteractionValid(interaction, hit.dateProfile, context)) { + isInvalid = true; + mutation = null; + mutatedRelevantEvents = null; + interaction.mutatedEvents = null; + } + } + if (mutatedRelevantEvents) { + context.dispatch({ + type: 'SET_EVENT_RESIZE', + state: interaction, + }); + } + else { + context.dispatch({ type: 'UNSET_EVENT_RESIZE' }); + } + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + if (mutation && isHitsEqual(initialHit, hit)) { + mutation = null; + } + this.validMutation = mutation; + this.mutatedRelevantEvents = mutatedRelevantEvents; + } + }; + this.handleDragEnd = (ev) => { + let { context } = this.component; + let eventDef = this.eventRange.def; + let eventInstance = this.eventRange.instance; + let eventApi = new EventImpl(context, eventDef, eventInstance); + let relevantEvents = this.relevantEvents; + let mutatedRelevantEvents = this.mutatedRelevantEvents; + context.emitter.trigger('eventResizeStop', { + el: this.draggingSegEl, + event: eventApi, + jsEvent: ev.origEvent, // Is this always a mouse event? See #4655 + view: context.viewApi, + }); + if (this.validMutation) { + let updatedEventApi = new EventImpl(context, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null); + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }); + let eventChangeData = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance), + revert() { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change events + }); + }, + }; + context.emitter.trigger('eventResize', { + ...eventChangeData, + el: this.draggingSegEl, + startDelta: this.validMutation.startDelta || createDuration(0), + endDelta: this.validMutation.endDelta || createDuration(0), + jsEvent: ev.origEvent, + view: context.viewApi, + }); + context.emitter.trigger('eventChange', eventChangeData); + } + else { + context.emitter.trigger('_noEventResize'); + } + // reset all internal state + this.draggingEventRange = null; + this.relevantEvents = null; + this.validMutation = null; + // okay to keep eventInstance around. useful to set it in handlePointerDown + }; + let { component } = settings; + let dragging = this.dragging = new FeaturefulElementDragging(settings.el); + dragging.pointer.selector = `.${classNames.internalEventResizer}`; + dragging.touchScrollAllowed = false; + dragging.autoScroller.isEnabled = component.context.options.dragScroll; + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', this.handlePointerDown); + hitDragging.emitter.on('dragstart', this.handleDragStart); + hitDragging.emitter.on('hitupdate', this.handleHitUpdate); + hitDragging.emitter.on('dragend', this.handleDragEnd); + } + destroy() { + this.dragging.destroy(); + } + querySegEl(ev) { + return ev.subjectEl.closest(`.${classNames.internalEvent}`); + } + } + function computeMutation(hit0, hit1, isFromStart, instanceRange) { + let dateEnv = hit0.context.dateEnv; + let date0 = hit0.dateSpan.range.start; + let date1 = hit1.dateSpan.range.start; + let delta = diffDates(date0, date1, dateEnv, hit0.largeUnit); + if (isFromStart) { + if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) { + return { startDelta: delta }; + } + } + else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) { + return { endDelta: delta }; + } + return null; + } + + class UnselectAuto { + constructor(context) { + this.context = context; + this.isRecentPointerDateSelect = false; // wish we could use a selector to detect date selection, but uses hit system + this.matchesCancel = false; + this.matchesEvent = false; + this.onSelect = (selectInfo) => { + if (selectInfo.jsEvent) { + this.isRecentPointerDateSelect = true; + } + }; + this.onDocumentPointerDown = (pev) => { + let unselectCancel = this.context.options.unselectCancel; + let downEl = getEventTargetViaRoot(pev.origEvent); + this.matchesCancel = !!downEl.closest(unselectCancel); + this.matchesEvent = !!downEl.closest(EventDragging.SELECTOR); // interaction started on an event? + }; + this.onDocumentPointerUp = (pev) => { + let { context } = this; + let { documentPointer } = this; + let calendarState = context.getCurrentData(); + // touch-scrolling should never unfocus any type of selection + if (!documentPointer.wasTouchScroll) { + if (calendarState.dateSelection && // an existing date selection? + !this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp? + ) { + let unselectAuto = context.options.unselectAuto; + if (unselectAuto && (!unselectAuto || !this.matchesCancel)) { + context.calendarApi.unselect(pev); + } + } + if (calendarState.eventSelection && // an existing event selected? + !this.matchesEvent // interaction DIDN'T start on an event + ) { + context.dispatch({ type: 'UNSELECT_EVENT' }); + } + } + this.isRecentPointerDateSelect = false; + }; + let documentPointer = this.documentPointer = new PointerDragging(document); + documentPointer.shouldIgnoreMove = true; + documentPointer.shouldWatchScroll = false; + documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown); + documentPointer.emitter.on('pointerup', this.onDocumentPointerUp); + /* + TODO: better way to know about whether there was a selection with the pointer + */ + context.emitter.on('select', this.onSelect); + } + destroy() { + this.context.emitter.off('select', this.onSelect); + this.documentPointer.destroy(); + } + } + + var interactionPlugin = { + name: 'interaction', + componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing], + calendarInteractions: [UnselectAuto], + elementDraggingImpl: FeaturefulElementDragging, + }; + + /* + Information about what will happen when an external element is dragged-and-dropped + onto a calendar. Contains information for creating an event. + */ + const DRAG_META_REFINERS = { + startTime: createDuration, + duration: createDuration, + create: Boolean, + sourceId: String, + }; + function parseDragMeta(raw) { + let { refined, extra } = refineProps(raw, DRAG_META_REFINERS); + return { + startTime: refined.startTime || null, + duration: refined.duration || null, + create: refined.create != null ? refined.create : true, + sourceId: refined.sourceId, + leftoverProps: extra, + }; + } + + /* + Given an already instantiated draggable object for one-or-more elements, + Interprets any dragging as an attempt to drag an events that lives outside + of a calendar onto a calendar. + */ + class ExternalElementDragging { + constructor(dragging, suppliedDragMeta) { + this.receivingContext = null; + this.droppableEvent = null; // will exist for all drags, even if create:false + this.suppliedDragMeta = null; + this.dragMeta = null; + this.handleDragStart = (ev) => { + this.dragMeta = this.buildDragMeta(ev.subjectEl); + }; + this.handleHitUpdate = (hit, isFinal, ev) => { + let { dragging } = this.hitDragging; + let receivingContext = null; + let droppableEvent = null; + let isInvalid = false; + let interaction = { + affectedEvents: createEmptyEventStore(), + mutatedEvents: createEmptyEventStore(), + isEvent: this.dragMeta.create, + }; + if (hit) { + receivingContext = hit.context; + if (this.canDropElOnCalendar(ev.subjectEl, receivingContext)) { + droppableEvent = computeEventForDateSpan(hit.dateSpan, this.dragMeta, receivingContext); + interaction.mutatedEvents = eventTupleToStore(droppableEvent); + isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext); + if (isInvalid) { + interaction.mutatedEvents = createEmptyEventStore(); + droppableEvent = null; + } + } + } + this.displayDrag(receivingContext, interaction); + // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?) + // TODO: wish we could somehow wait for dispatch to guarantee render + dragging.setMirrorIsVisible(isFinal || + !droppableEvent || + !document.querySelector(`.${classNames.internalEventMirror}`)); + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + dragging.setMirrorNeedsRevert(!droppableEvent); + this.receivingContext = receivingContext; + this.droppableEvent = droppableEvent; + } + }; + this.handleDragEnd = (pev) => { + let { receivingContext, droppableEvent } = this; + this.clearDrag(); + if (receivingContext && droppableEvent) { + let finalHit = this.hitDragging.finalHit; + let finalView = finalHit.context.viewApi; + let dragMeta = this.dragMeta; + receivingContext.emitter.trigger('drop', { + ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), + draggedEl: pev.subjectEl, + jsEvent: pev.origEvent, // Is this always a mouse event? See #4655 + view: finalView, + }); + if (dragMeta.create) { + let addingEvents = eventTupleToStore(droppableEvent); + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: addingEvents, + }); + if (pev.isTouch) { + receivingContext.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: droppableEvent.instance.instanceId, + }); + } + // signal that an external event landed + receivingContext.emitter.trigger('eventReceive', { + event: new EventImpl(receivingContext, droppableEvent.def, droppableEvent.instance), + relatedEvents: [], + revert() { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: addingEvents, + }); + }, + draggedEl: pev.subjectEl, + view: finalView, + }); + } + } + this.receivingContext = null; + this.droppableEvent = null; + }; + let hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore); + hitDragging.requireInitial = false; // will start outside of a component + hitDragging.emitter.on('dragstart', this.handleDragStart); + hitDragging.emitter.on('hitupdate', this.handleHitUpdate); + hitDragging.emitter.on('dragend', this.handleDragEnd); + this.suppliedDragMeta = suppliedDragMeta; + } + buildDragMeta(subjectEl) { + if (typeof this.suppliedDragMeta === 'object') { + return parseDragMeta(this.suppliedDragMeta); + } + if (typeof this.suppliedDragMeta === 'function') { + return parseDragMeta(this.suppliedDragMeta(subjectEl)); + } + return getDragMetaFromEl(subjectEl); + } + displayDrag(nextContext, state) { + let prevContext = this.receivingContext; + if (prevContext && prevContext !== nextContext) { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }); + } + } + clearDrag() { + if (this.receivingContext) { + this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + } + canDropElOnCalendar(el, receivingContext) { + let dropAccept = receivingContext.options.dropAccept; + if (typeof dropAccept === 'function') { + return dropAccept.call(receivingContext.calendarApi, el); + } + if (typeof dropAccept === 'string' && dropAccept) { + return el.matches(dropAccept); + } + return true; + } + } + // Utils for computing event store from the DragMeta + // ---------------------------------------------------------------------------------------------------- + function computeEventForDateSpan(dateSpan, dragMeta, context) { + let defProps = { ...dragMeta.leftoverProps }; + for (let transform of context.pluginHooks.externalDefTransforms) { + Object.assign(defProps, transform(dateSpan, dragMeta)); + } + let { refined, extra } = refineEventDef(defProps, context); + let def = parseEventDef(refined, extra, dragMeta.sourceId, dateSpan.allDay, context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd + context); + let start = dateSpan.range.start; + // only rely on time info if drop zone is all-day, + // otherwise, we already know the time + if (dateSpan.allDay && dragMeta.startTime) { + start = context.dateEnv.add(start, dragMeta.startTime); + } + let end = dragMeta.duration ? + context.dateEnv.add(start, dragMeta.duration) : + getDefaultEventEnd(dateSpan.allDay, start, context); + let instance = createEventInstance(def.defId, { start, end }); + return { def, instance }; + } + // Utils for extracting data from element + // ---------------------------------------------------------------------------------------------------- + function getDragMetaFromEl(el) { + let str = getEmbeddedElData(el, 'event'); + let obj = str ? + JSON.parse(str) : + { create: false }; // if no embedded data, assume no event creation + return parseDragMeta(obj); + } + config.dataAttrPrefix = ''; + function getEmbeddedElData(el, name) { + let prefix = config.dataAttrPrefix; + let prefixedName = (prefix ? prefix + '-' : '') + name; + return el.getAttribute('data-' + prefixedName) || ''; + } + + /* + Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements. + The third-party system is responsible for drawing the visuals effects of the drag. + This class simply monitors for pointer movements and fires events. + It also has the ability to hide the moving element (the "mirror") during the drag. + */ + class InferredElementDragging extends ElementDragging { + constructor(containerEl) { + super(containerEl); + this.shouldIgnoreMove = false; + this.mirrorSelector = ''; + this.currentMirrorEl = null; + this.handlePointerDown = (ev) => { + this.emitter.trigger('pointerdown', ev); + if (!this.shouldIgnoreMove) { + // fire dragstart right away. does not support delay or min-distance + this.emitter.trigger('dragstart', ev); + } + }; + this.handlePointerMove = (ev) => { + if (!this.shouldIgnoreMove) { + this.emitter.trigger('dragmove', ev); + } + }; + this.handlePointerUp = (ev) => { + this.emitter.trigger('pointerup', ev); + if (!this.shouldIgnoreMove) { + // fire dragend right away. does not support a revert animation + this.emitter.trigger('dragend', ev); + } + }; + let pointer = this.pointer = new PointerDragging(containerEl); + pointer.emitter.on('pointerdown', this.handlePointerDown); + pointer.emitter.on('pointermove', this.handlePointerMove); + pointer.emitter.on('pointerup', this.handlePointerUp); + } + destroy() { + this.pointer.destroy(); + } + cancel() { + this.shouldIgnoreMove = true; + } + setMirrorIsVisible(bool) { + if (bool) { + // restore a previously hidden element. + // use the reference in case the selector class has already been removed. + if (this.currentMirrorEl) { + this.currentMirrorEl.style.visibility = ''; + this.currentMirrorEl = null; + } + } + else { + let mirrorEl = this.mirrorSelector + // TODO: somehow query FullCalendars WITHIN shadow-roots + ? document.querySelector(this.mirrorSelector) + : null; + if (mirrorEl) { + this.currentMirrorEl = mirrorEl; + mirrorEl.style.visibility = 'hidden'; + } + } + } + } + + /* + Bridges third-party drag-n-drop systems with FullCalendar. + Must be instantiated and destroyed by caller. + */ + class ThirdPartyDraggable { + constructor(containerOrSettings, settings) { + let containerEl = document; + if ( + // wish we could just test instanceof EventTarget, but doesn't work in IE11 + containerOrSettings === document || + containerOrSettings instanceof Element) { + containerEl = containerOrSettings; + settings = settings || {}; + } + else { + settings = (containerOrSettings || {}); + } + let dragging = this.dragging = new InferredElementDragging(containerEl); + if (typeof settings.itemSelector === 'string') { + dragging.pointer.selector = settings.itemSelector; + } + else if (containerEl === document) { + dragging.pointer.selector = '[data-event]'; + } + if (typeof settings.mirrorSelector === 'string') { + dragging.mirrorSelector = settings.mirrorSelector; + } + let externalDragging = new ExternalElementDragging(dragging, settings.eventData); + // The hit-detection system requires that the dnd-mirror-element be pointer-events:none, + // but this can't be guaranteed for third-party draggables, so disable + externalDragging.hitDragging.disablePointCheck = true; + } + destroy() { + this.dragging.destroy(); + } + } + + /* + Makes an element (that is *external* to any calendar) draggable. + Can pass in data that determines how an event will be created when dropped onto a calendar. + Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system. + */ + class ExternalDraggable { + constructor(el, settings = {}) { + this.handlePointerDown = (ev) => { + let { dragging } = this; + let { minDistance, longPressDelay } = this.settings; + dragging.minDistance = + minDistance != null ? + minDistance : + (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance); + dragging.delay = + ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv + (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) : + 0; + }; + this.handleDragStart = (ev) => { + if (ev.isTouch && + this.dragging.delay && + ev.subjectEl.classList.contains(classNames.internalEvent)) { + this.dragging.mirror.getMirrorEl().classList.add(classNames.internalEventSelected); + } + }; + this.settings = settings; + let dragging = this.dragging = new FeaturefulElementDragging(el); + dragging.touchScrollAllowed = false; + if (settings.itemSelector != null) { + dragging.pointer.selector = settings.itemSelector; + } + if (settings.appendTo != null) { + dragging.mirror.parentNode = settings.appendTo; // TODO: write tests + } + dragging.emitter.on('pointerdown', this.handlePointerDown); + dragging.emitter.on('dragstart', this.handleDragStart); + new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new + } + destroy() { + this.dragging.destroy(); + } + } + + var interaction = /*#__PURE__*/Object.freeze({ + __proto__: null, + Draggable: ExternalDraggable, + ThirdPartyDraggable: ThirdPartyDraggable, + 'default': interactionPlugin + }); + + class TableDateProfileGenerator extends DateProfileGenerator { + // Computes the date range that will be rendered + buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) { + let renderRange = super.buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay); + let { props } = this; + return buildDayTableRenderRange({ + currentRange: renderRange, // ??? + snapToWeek: /^(year|month)$/.test(currentRangeUnit), + fixedWeekCount: props.fixedWeekCount, + dateEnv: props.dateEnv, + }); + } + } + function buildDayTableRenderRange(props) { + let { dateEnv, currentRange } = props; + let { start, end } = currentRange; + let endOfWeek; + // year and month views should be aligned with weeks. this is already done for week + if (props.snapToWeek) { + start = dateEnv.startOfWeek(start); + // make end-of-week if not already + endOfWeek = dateEnv.startOfWeek(end); + if (endOfWeek.valueOf() !== end.valueOf()) { + end = addWeeks(endOfWeek, 1); + } + } + // ensure 6 weeks + if (props.fixedWeekCount) { + // TODO: instead of these date-math gymnastics (for multimonth view), + // compute dateprofiles of all months, then use start of first and end of last. + let lastMonthRenderStart = dateEnv.startOfWeek(dateEnv.startOfMonth(addDays(currentRange.end, -1))); + let rowCount = Math.ceil(// could be partial weeks due to hiddenDays + diffWeeks(lastMonthRenderStart, end)); + end = addWeeks(end, 6 - rowCount); + } + return { start, end }; + } + + class DayGridView extends BaseComponent { + constructor() { + super(...arguments); + // memo + this.buildDayTableModel = memoize(buildDayTableModel); + this.buildDateRowConfigs = memoize(buildDateRowConfigs); + this.createDayHeaderFormatter = memoize(createDayHeaderFormatter); + // internal + this.slicer = new DayTableSlicer(); + } + render() { + const { props, context } = this; + const { dateProfile } = props; + const { options, dateEnv } = context; + const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator, dateEnv); + const datesRepDistinctDays = dayTableModel.rowCount === 1; + const dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dayTableModel.colCount); + const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel); + return (u$1(NowTimer, { unit: "day", children: (nowDate, todayRange) => { + const headerTiers = this.buildDateRowConfigs(dayTableModel.headerDates, datesRepDistinctDays, dateProfile, todayRange, dayHeaderFormat, context); + return (u$1(DayGridLayout, { labelId: props.labelId, labelStr: props.labelStr, dateProfile: dateProfile, todayRange: todayRange, cellRows: dayTableModel.cellRows, forPrint: props.forPrint, className: props.className, + // header content + headerTiers: headerTiers, + // body content + fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection })); + } })); + } + } + + var dayGridPlugin = { + name: 'daygrid', + initialView: 'dayGridMonth', + views: { + dayGrid: { + component: DayGridView, + dateProfileGeneratorClass: TableDateProfileGenerator, + }, + dayGridDay: { + type: 'dayGrid', + duration: { days: 1 }, + }, + dayGridWeek: { + type: 'dayGrid', + duration: { weeks: 1 }, + }, + dayGridMonth: { + type: 'dayGrid', + duration: { months: 1 }, + fixedWeekCount: true, + }, + dayGridYear: { + type: 'dayGrid', + duration: { years: 1 }, + }, + }, + }; + + var daygrid = /*#__PURE__*/Object.freeze({ + __proto__: null, + 'default': dayGridPlugin + }); + + class TimeGridView extends DateComponent { + constructor() { + super(...arguments); + // memo + this.createDayHeaderFormatter = memoize(createDayHeaderFormatter); + this.buildTimeColsModel = memoize(buildTimeColsModel); + this.buildDayRanges = memoize(buildDayRanges); + this.buildDateRowConfigs = memoize(buildDateRowConfigs); + this.splitFgEventSegs = memoize((organizeSegsByCol)); + this.splitBgEventSegs = memoize((organizeSegsByCol)); + this.splitBusinessHourSegs = memoize((organizeSegsByCol)); + this.splitNowIndicatorSegs = memoize((organizeSegsByCol)); + this.splitDateSelectionSegs = memoize((organizeSegsByCol)); + this.splitEventDrag = memoize(splitInteractionByCol); + this.splitEventResize = memoize(splitInteractionByCol); + // internal + this.allDaySplitter = new AllDaySplitter(); + this.dayTableSlicer = new DayTableSlicer(); + this.dayTimeColsSlicer = new DayTimeColsSlicer(); + } + render() { + const { props, context } = this; + const { dateProfile } = props; + const { options, dateProfileGenerator } = context; + const dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator, context.dateEnv); + const dayRanges = this.buildDayRanges(dayTableModel, dateProfile, context.dateEnv); + const splitProps = this.allDaySplitter.splitProps(props); + const allDayProps = this.dayTableSlicer.sliceProps(splitProps.allDay, dateProfile, options.nextDayThreshold, context, dayTableModel); + const timedProps = this.dayTimeColsSlicer.sliceProps(splitProps.timed, dateProfile, null, context, dayRanges); + const dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, true, // datesRepDistinctDays + dayTableModel.colCount); + return (u$1(NowTimer, { unit: options.nowIndicator ? 'minute' : 'day' /* hacky */, children: (nowDate, todayRange) => { + const colCount = dayTableModel.cellRows[0].length; + const nowIndicatorSeg = !props.forPrint && options.nowIndicator && + this.dayTimeColsSlicer.sliceNowDate(nowDate, dateProfile, options.nextDayThreshold, context, dayRanges); + const fgEventSegsByCol = this.splitFgEventSegs(timedProps.fgEventSegs, colCount); + const bgEventSegsByCol = this.splitBgEventSegs(timedProps.bgEventSegs, colCount); + const businessHourSegsByCol = this.splitBusinessHourSegs(timedProps.businessHourSegs, colCount); + const nowIndicatorSegsByCol = this.splitNowIndicatorSegs(nowIndicatorSeg, colCount); + const dateSelectionSegsByCol = this.splitDateSelectionSegs(timedProps.dateSelectionSegs, colCount); + const eventDragByCol = this.splitEventDrag(timedProps.eventDrag, colCount); + const eventResizeByCol = this.splitEventResize(timedProps.eventResize, colCount); + const headerTiers = this.buildDateRowConfigs(dayTableModel.headerDates, true, // datesRepDistinctDays + props.dateProfile, todayRange, dayHeaderFormat, context); + return (u$1(TimeGridLayout, { labelId: props.labelId, labelStr: props.labelStr, dateProfile: dateProfile, nowDate: nowDate, todayRange: todayRange, cells: dayTableModel.cellRows[0], forPrint: props.forPrint, className: props.className, + // header content + headerTiers: headerTiers, + // all-day content + fgEventSegs: allDayProps.fgEventSegs, bgEventSegs: allDayProps.bgEventSegs, businessHourSegs: allDayProps.businessHourSegs, dateSelectionSegs: allDayProps.dateSelectionSegs, eventDrag: allDayProps.eventDrag, eventResize: allDayProps.eventResize, + // timed content + fgEventSegsByCol: fgEventSegsByCol, bgEventSegsByCol: bgEventSegsByCol, businessHourSegsByCol: businessHourSegsByCol, nowIndicatorSegsByCol: nowIndicatorSegsByCol, dateSelectionSegsByCol: dateSelectionSegsByCol, eventDragByCol: eventDragByCol, eventResizeByCol: eventResizeByCol, + // universal content + eventSelection: props.eventSelection })); + } })); + } + } + + var timeGridPlugin = { + name: 'timegrid', + initialView: 'timeGridWeek', + deps: [dayGridPlugin], + views: { + timeGrid: { + component: TimeGridView, + usesMinMaxTime: true, // indicates that slotMinTime/slotMaxTime affects rendering + allDaySlot: true, + slotDuration: '00:30:00', + slotEventOverlap: true, // a bad name. confused with overlap/constraint system + }, + timeGridDay: { + type: 'timeGrid', + duration: { days: 1 }, + }, + timeGridWeek: { + type: 'timeGrid', + duration: { weeks: 1 }, + }, + }, + }; + + var timegrid = /*#__PURE__*/Object.freeze({ + __proto__: null, + 'default': timeGridPlugin + }); + + class ListDayHeaderInner extends BaseComponent { + render() { + const { props, context } = this; + const { options } = context; + const textParts = context.dateEnv.formatToParts(props.dayDate, props.dayFormat); + const text = joinDateTimeFormatParts(textParts); + const hasNavLink = options.navLinks; + const renderProps = { + ...props.dateMeta, + view: context.viewApi, + text, + textParts, + get weekdayText() { return findWeekdayText(textParts); }, + get dayNumberText() { return findDayNumberText(textParts); }, + hasNavLink, + level: props.level, + }; + const navLinkAttrs = hasNavLink + ? buildNavLinkAttrs(this.context, props.dayDate, undefined, text, this.props.isTabbable) + : {}; + return (u$1(ContentContainer, { tag: "div", attrs: navLinkAttrs, renderProps: renderProps, generatorName: "listDayHeaderContent", customGenerator: options.listDayHeaderContent, defaultGenerator: renderText$1, classNameGenerator: options.listDayHeaderInnerClass })); + } + } + + class ListDayHeader extends BaseComponent { + render() { + let { options, viewApi, viewSpec } = this.context; + let { dayDate, dateMeta } = this.props; + let stickyHeaderDates = !this.props.forPrint; + const listDayFormat = options.listDayFormat ?? createDefaultListDayFormat(viewSpec); + const listDayAltFormat = options.listDayAltFormat ?? createDefaultListDaySideFormat(viewSpec); + let renderProps = { + ...dateMeta, + view: viewApi, + }; + return (u$1(ContentContainer, { tag: "div", attrs: { + 'data-date': formatDayString(dayDate), + ...(dateMeta.isToday ? { 'aria-current': 'date' } : {}), + }, className: stickyHeaderDates ? classNames.stickyT : '', renderProps: renderProps, generatorName: undefined, classNameGenerator: options.listDayHeaderClass, didMount: options.listDayHeaderDidMount, willUnmount: options.listDayHeaderWillUnmount, children: () => (u$1(S, { children: [Boolean(listDayFormat) && (u$1(ListDayHeaderInner, { dayDate: dayDate, dayFormat: listDayFormat, isTabbable: true, dateMeta: dateMeta, level: 0 })), Boolean(listDayAltFormat) && (u$1(ListDayHeaderInner, { dayDate: dayDate, dayFormat: listDayAltFormat, isTabbable: false, dateMeta: dateMeta, level: 1 }))] })) })); + } + } + function createDefaultListDayFormat({ durationUnit, singleUnit }) { + if (singleUnit === 'day') { + return WEEKDAY_ONLY_FORMAT; + } + else if (durationUnit === 'day' || singleUnit === 'week') { + return WEEKDAY_ONLY_FORMAT; + } + else { + return FULL_DATE_FORMAT; + } + } + function createDefaultListDaySideFormat({ durationUnit, singleUnit }) { + if (singleUnit === 'day') ; + else if (durationUnit === 'day' || singleUnit === 'week') { + return FULL_DATE_FORMAT; + } + else { + return WEEKDAY_ONLY_FORMAT; + } + } + + const DEFAULT_TIME_FORMAT = createFormatter({ + hour: 'numeric', + minute: '2-digit', + meridiem: 'short', + }); + class ListEvent extends BaseComponent { + render() { + let { props, context } = this; + let { eventRange } = props; + const { displayEventTime } = context.options; + let forcedTimeText = (displayEventTime !== false) && (eventRange.def.allDay || (!props.isStart && !props.isEnd)) + ? context.options.allDayText + : undefined; + return (u$1(StandardEvent, { ...props, attrs: { + role: 'listitem', + }, forcedTimeText: forcedTimeText, defaultTimeFormat: DEFAULT_TIME_FORMAT, disableDragging: true, disableResizing: true, disableZindexes // because conflicts with sticky list headers + : true, display: 'list-item' })); + } + } + + class ListDay extends BaseComponent { + constructor() { + super(...arguments); + // memo + this.getDateMeta = memoize(getDateMeta); + this.sortEventSegs = memoize(sortEventSegs); + } + render() { + const { props, context } = this; + const { nowDate, todayRange } = props; + const { options } = context; + const dateMeta = this.getDateMeta(props.dayDate, context.dateEnv, undefined, todayRange); + const segs = this.sortEventSegs(props.segs, options.eventOrder); + const fullDateStr = buildDateStr(this.context, props.dayDate); + const listDayData = { + ...dateMeta, + isFirst: props.isFirst, + isLast: props.isLast, + view: context.viewApi, + }; + const listDayEventsData = { + ...dateMeta, + view: context.viewApi, + }; + return (u$1("div", { role: 'listitem', "aria-label": fullDateStr, className: generateClassName(options.listDayClass, listDayData), children: [u$1(ListDayHeader, { dayDate: props.dayDate, dateMeta: dateMeta, forPrint: props.forPrint }), u$1("div", { role: 'list', "aria-label": options.eventsHint, className: joinClassNames(generateClassName(options.listDayBodyClass, listDayEventsData), classNames.flexCol), children: segs.map((seg, index) => { + const key = getEventKey(seg); + const isFirst = index === 0; + const isLast = index === segs.length - 1; + return (u$1(ListEvent, { eventRange: seg.eventRange, slicedStart: seg.slicedStart, slicedEnd: seg.slicedEnd, isStart: seg.isStart, isEnd: seg.isEnd, isFirst: isFirst, isLast: isLast, isDragging: false, isResizing: false, isMirror: false, isSelected: false, ...getEventRangeMeta(seg.eventRange, todayRange, nowDate) }, key)); + }) })] })); + } + } + + /* + Responsible for the scroller, and forwarding event-related actions into the "grid". + */ + class ListView extends DateComponent { + constructor() { + super(...arguments); + // memo + this.computeDateVars = memoize(computeDateVars); + this.eventStoreToSegs = memoize(this._eventStoreToSegs); + this.setRootEl = (rootEl) => { + if (rootEl) { + this.context.registerInteractiveComponent(this, { + el: rootEl, + disableHits: true, // HACK to not do date-clicking/selecting + }); + } + else { + this.context.unregisterInteractiveComponent(this); + } + }; + } + render() { + let { props, context } = this; + let { options } = context; + let { dayDates, dayRanges } = this.computeDateVars(props.dateProfile); + let eventSegs = this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges); + let verticalScrolling = !props.forPrint && !getIsHeightAuto(options); + return (u$1(ViewContainer, { viewSpec: context.viewSpec, className: joinClassNames(props.className, classNames.flexCol), elRef: this.setRootEl, children: eventSegs.length ? (u$1(Scroller // TODO: don't need heavyweight component + , { vertical: verticalScrolling, className: joinClassNames(classNames.flexCol, verticalScrolling ? classNames.liquid : ''), children: this.renderSegList(eventSegs, dayDates) })) : this.renderEmptyMessage() })); + } + renderEmptyMessage() { + let { options, viewApi } = this.context; + let renderProps = { + text: options.noEventsText, + view: viewApi, + }; + return (u$1(ContentContainer, { tag: "div", attrs: { + role: 'status', // does a polite announcement + }, renderProps: renderProps, generatorName: "noEventsContent", customGenerator: options.noEventsContent, defaultGenerator: renderNoEventsInner, classNameGenerator: options.noEventsClass, className: classNames.grow, didMount: options.noEventsDidMount, willUnmount: options.noEventsWillUnmount, children: (InnerContent) => (u$1(InnerContent, { tag: "div", className: generateClassName(options.noEventsInnerClass, renderProps) })) })); + } + renderSegList(allSegs, dayDates) { + let { options } = this.context; + let segsByDay = groupSegsByDay(allSegs); // sparse array + return (u$1("div", { role: "list", "aria-labelledby": this.props.labelId, "aria-label": this.props.labelStr, className: joinClassNames(classNames.flexCol, joinClassNames(options.listDaysClass)), children: u$1(NowTimer, { unit: "day", children: (nowDate, todayRange) => { + const dayNodes = []; + const populatedDayCount = segsByDay.reduce((count, daySegs) => count + (daySegs ? 1 : 0), 0); + let populatedDayIndex = 0; + for (let dayIndex = 0; dayIndex < segsByDay.length; dayIndex += 1) { + let daySegs = segsByDay[dayIndex]; + if (daySegs) { // sparse array, so might be undefined + const dayDate = dayDates[dayIndex]; + const key = formatDayString(dayDate); + const isFirst = populatedDayIndex === 0; + const isLast = populatedDayIndex === populatedDayCount - 1; + dayNodes.push(u$1(ListDay, { dayDate: dayDate, nowDate: nowDate, todayRange: todayRange, segs: daySegs, isFirst: isFirst, isLast: isLast, forPrint: this.props.forPrint }, key)); + populatedDayIndex += 1; + } + } + return (u$1(S, { children: dayNodes })); + } }) })); + } + _eventStoreToSegs(eventStore, eventUiBases, dayRanges) { + return this.eventRangesToSegs(sliceEventStore(eventStore, eventUiBases, + // HACKY to reference internal state... + this.props.dateProfile.activeRange, this.context.options.nextDayThreshold).fg, dayRanges); + } + eventRangesToSegs(fullDayEventRanges, dayRanges) { + let segs = []; + for (let fullDayEventRange of fullDayEventRanges) { + segs.push(...this.eventRangeToSegs(fullDayEventRange, dayRanges)); + } + return segs; + } + eventRangeToSegs(fullDayEventRange, dayRanges) { + let fullDayRange = fullDayEventRange.range; + let dayIndex; + let segs = []; + for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex += 1) { + const slicedFullDayRange = intersectRanges(fullDayRange, dayRanges[dayIndex]); + if (slicedFullDayRange) { + segs.push({ + eventRange: fullDayEventRange, + slicedStart: slicedFullDayRange.start, + slicedEnd: slicedFullDayRange.end, + isStart: fullDayEventRange.isStart && fullDayRange.start.valueOf() === slicedFullDayRange.start.valueOf(), + isEnd: fullDayEventRange.isEnd && fullDayRange.end.valueOf() === slicedFullDayRange.end.valueOf(), + dayIndex, + }); + } + } + return segs; + } + } + function renderNoEventsInner(renderProps) { + return renderProps.text; + } + function computeDateVars(dateProfile) { + let dayStart = startOfDay(dateProfile.renderRange.start); + let viewEnd = dateProfile.renderRange.end; + let dayDates = []; + let dayRanges = []; + while (dayStart < viewEnd) { + dayDates.push(dayStart); + dayRanges.push({ + start: dayStart, + end: addDays(dayStart, 1), + }); + dayStart = addDays(dayStart, 1); + } + return { dayDates, dayRanges }; + } + // Returns a sparse array of arrays, segs grouped by their dayIndex + function groupSegsByDay(segs) { + let segsByDay = []; // sparse array + let i; + let seg; + for (i = 0; i < segs.length; i += 1) { + seg = segs[i]; + (segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = [])) + .push(seg); + } + return segsByDay; + } + + var listPlugin = { + name: 'list', + views: { + list: { + component: ListView, + buttonTextKey: 'listText', // what to lookup in locale files + disallowAmbigTitle: true, + }, + listDay: { + type: 'list', + duration: { days: 1 }, + }, + listWeek: { + type: 'list', + duration: { weeks: 1 }, + }, + listMonth: { + type: 'list', + duration: { month: 1 }, + }, + listYear: { + type: 'list', + duration: { year: 1 }, + }, + }, + }; + + var list = /*#__PURE__*/Object.freeze({ + __proto__: null, + 'default': listPlugin + }); + + class SingleMonth extends DateComponent { + constructor() { + super(...arguments); + this.state = {}; + // memo + this.buildDayTableModel = memoize(buildDayTableModel); + this.createDayHeaderFormatter = memoize(createDayHeaderFormatter); + this.buildDateRowConfig = memoize(buildDateRowConfig); + // ref + this.titleElRef = M$1(); + this.tableHeaderElRef = M$1(); + this.rowHeightRefMap = new RefMap(() => { + afterSize(this.handleHeights); + }); + this.slicer = new DayTableSlicer(); + this.handleEl = (el) => { + const { options } = this.context; + if (el) { + this.rootEl = el; + options.singleMonthDidMount?.({ + el: this.rootEl, + ...this.renderProps, + }); + } + }; + this.handleGridWidth = (gridWidth) => { + if (this._isUnmounting) + return; + this.setState({ gridWidth }); + }; + this.handleHeights = () => { + if (this._isUnmounting) + return; + setRef(this.props.heightsRef, { + titleHeight: this.titleHeight, + tableHeaderHeight: this.tableHeaderHeight, + rowHeightMap: this.rowHeightRefMap.current, + cellRows: this.cellRows, + }); + }; + } + get titleId() { + return this.context.baseId + 'month-' + this.props.isoDateStr; + } + render() { + const { props, state, context } = this; + const { dateProfile, forPrint } = props; + const { options, dateEnv } = context; + const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options); + const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator, dateEnv); + const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel); + const dayHeaderFormat = this.createDayHeaderFormatter(options.dayHeaderFormat, false, // datesRepDistinctDays + dayTableModel.colCount); + const rowConfig = this.buildDateRowConfig(dayTableModel.headerDates, false, // datesRepDistinctDays + dateProfile, props.todayRange, dayHeaderFormat, context); + this.cellRows = dayTableModel.cellRows; + const isTitleAndHeaderSticky = !forPrint && props.colCount === 1; + const isAspectRatio = !forPrint || props.hasLateralSiblings; + const cellColCnt = dayTableModel.cellRows[0].length; + const colWidth = state.gridWidth != null ? state.gridWidth / cellColCnt : undefined; + const cellIsMicro = colWidth != null && colWidth <= dayMicroWidth; + const cellIsNarrow = cellIsMicro || (colWidth != null && colWidth <= options.dayNarrowWidth); + const rowHeightGuess = state.gridWidth != null + ? (1 / options.aspectRatio) * state.gridWidth / 6 + : undefined; + const headerStickyBottom = isTitleAndHeaderSticky + ? rowHeightGuess + : undefined; + const titleStickyBottom = isTitleAndHeaderSticky && rowHeightGuess != null && state.tableHeaderHeight != null + ? rowHeightGuess + state.tableHeaderHeight + 1 + : undefined; + const businessHourSegs = forPrint ? [] : slicedProps.businessHourSegs; + const dateSelectionSegs = forPrint ? [] : slicedProps.dateSelectionSegs; + const eventDrag = forPrint ? null : slicedProps.eventDrag; + const eventResize = forPrint ? null : slicedProps.eventResize; + const hasNavLink = options.navLinks && props.colCount > 1; + const headerRenderProps = { + multiMonthColumns: props.colCount || 0, + isSticky: isTitleAndHeaderSticky, + isNarrow: cellIsNarrow, + hasNavLink, + }; + const monthStartDate = props.dateProfile.currentRange.start; + const navLinkAttrs = hasNavLink + ? buildNavLinkAttrs(context, monthStartDate, 'month', props.isoDateStr) + : {}; + return (u$1("div", { role: 'listitem', style: { width: props.width }, children: u$1("div", { role: 'grid', "aria-labelledby": this.titleId, "data-date": props.isoDateStr, className: joinClassNames(generateClassName(options.singleMonthClass, { + isFirst: props.isFirst, + isLast: props.isLast, + multiMonthColumns: props.colCount || 0, + }), classNames.flexCol, props.hasLateralSiblings && classNames.breakInsideAvoid), children: [u$1(Ruler, { widthRef: this.handleGridWidth }), u$1("div", { id: this.titleId, ref: this.titleElRef, className: joinClassNames(generateClassName(options.singleMonthHeaderClass, headerRenderProps), isTitleAndHeaderSticky && classNames.stickyT, classNames.flexCol), style: { + // HACK to keep zIndex above table-header, + // because in Chrome, something about position:sticky on this title div + // causes its bottom border to no be considered part of its mass, + // and would get overlapped and hidden by the table-header div + zIndex: isTitleAndHeaderSticky ? 3 : undefined, // TODO: className? + marginBottom: titleStickyBottom, + }, children: u$1("div", { ...navLinkAttrs, className: joinClassNames(generateClassName(options.singleMonthHeaderInnerClass, headerRenderProps), navLinkAttrs.className), children: joinDateTimeFormatParts(dateEnv.formatToParts(monthStartDate, props.titleFormat)) }) }), u$1("div", { className: joinClassNames(generateClassName(options.tableClass, { + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: props.colCount || 0, + }), classNames.flexCol), style: { + marginTop: titleStickyBottom != null ? -titleStickyBottom : undefined, + }, children: [u$1("div", { ref: this.tableHeaderElRef, className: joinClassNames(generateClassName(options.tableHeaderClass, { + isSticky: isTitleAndHeaderSticky, + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: props.colCount || 0, + }), classNames.flexCol, isTitleAndHeaderSticky && classNames.sticky), style: { + zIndex: isTitleAndHeaderSticky ? 2 : undefined, // TODO: className? + top: isTitleAndHeaderSticky ? state.titleHeight : 0, + marginBottom: headerStickyBottom, + }, children: [u$1(DayGridHeaderRow, { ...rowConfig, role: 'row', borderBottom: false, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro, rowLevel: 0 }), u$1("div", { className: generateClassName(options.dayHeaderDividerClass, { + isSticky: isTitleAndHeaderSticky, + multiMonthColumns: props.colCount || 0, + options: { allDaySlot: Boolean(options.allDaySlot) }, + }) })] }), u$1("div", { className: joinClassNames(generateClassName(options.tableBodyClass, { + borderlessX, + borderlessTop, + borderlessBottom, + multiMonthColumns: props.colCount || 0, + }), classNames.flexCol, isAspectRatio && classNames.rel), style: { + zIndex: isTitleAndHeaderSticky ? 1 : undefined, // TODO: className? + marginTop: headerStickyBottom != null ? -headerStickyBottom : undefined, + aspectRatio: isAspectRatio ? String(options.aspectRatio) : undefined, + }, children: u$1(DayGridRows, { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, className: isAspectRatio ? classNames.fill : '', forPrint: forPrint && !props.hasLateralSiblings, dayMaxEventRows: (forPrint && props.hasLateralSiblings) + ? 1 // for side-by-side multimonths, limit to one row + : true // otherwise, always do +more link, never expand rows + , + // content + fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: businessHourSegs, dateSelectionSegs: dateSelectionSegs, eventDrag: eventDrag, eventResize: eventResize, eventSelection: slicedProps.eventSelection, + // dimensions + visibleWidth: state.gridWidth, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro, rowHeightRefMap: this.rowHeightRefMap }) })] })] }) })); + } + componentDidMount() { + this._isUnmounting = false; + this.disconnectTitleHeight = watchHeight(this.titleElRef.current, (height) => { + this.setState({ titleHeight: this.titleHeight = height }); + afterSize(this.handleHeights); + }); + this.disconnectTableHeaderHeight = watchHeight(this.tableHeaderElRef.current, (height) => { + this.setState({ tableHeaderHeight: this.tableHeaderHeight = height }); + afterSize(this.handleHeights); + }); + } + componentWillUnmount() { + const { options } = this.context; + this._isUnmounting = true; + this.disconnectTitleHeight(); + this.disconnectTableHeaderHeight(); + options.singleMonthWillUnmount?.({ + el: this.rootEl, + ...this.renderProps, + }); + } + } + + class MultiMonthView extends DateComponent { + constructor() { + super(...arguments); + this.state = {}; + // memo + this.splitDateProfileByMonth = memoize(splitDateProfileByMonth); + this.buildMonthFormat = memoize(buildMonthFormat); + // ref + this.scrollerRef = M$1(); + this.tilesElRef = M$1(); + this.scrollState = {}; + // Scrolling + // ----------------------------------------------------------------------------------------------- + this.handleInnerWidth = (innerWidth) => { + if (this._isUnmounting) + return; + this.setState({ innerWidth }); + }; + this.handleScrollStart = () => { + this.scrollState.date = undefined; + this.scrollState.top = undefined; + }; + this.handleScrollEnd = (isDevice) => { + const scroller = this.scrollerRef.current; + if (isDevice && scroller) { + this.scrollState.top = scroller.y; + this.scrollState.date = undefined; + } + }; + } + render() { + const { context, props, state } = this; + const { options } = context; + const verticalScrolling = !props.forPrint && !getIsHeightAuto(options); + const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, options.fixedWeekCount, options.showNonCurrentDates); + const monthTitleFormat = this.buildMonthFormat(options.singleMonthTitleFormat, monthDateProfiles); + const { multiMonthMaxColumns, singleMonthMinWidth } = options; + const { innerWidth } = state; + let cols; + let cssMonthWidth; + let hasLateralSiblings = false; + if (innerWidth != null) { + cols = Math.max(1, Math.min(multiMonthMaxColumns, Math.floor(innerWidth / singleMonthMinWidth))); + if (props.forPrint) { + cols = Math.min(cols, 2); + } + cssMonthWidth = fracToCssDim(1 / cols); + hasLateralSiblings = cols > 1; + } + return (u$1(NowTimer, { unit: "day", children: (nowDate, todayRange) => (u$1(ViewContainer, { viewSpec: context.viewSpec, className: joinClassNames( + // HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard: + // https://stackoverflow.com/a/60256345 + !props.forPrint && classNames.flexCol, props.className), children: [u$1(Scroller, { vertical: verticalScrolling, className: verticalScrolling ? classNames.liquid : '', ref: this.scrollerRef, children: u$1("div", { role: 'list', ref: this.tilesElRef, "aria-labelledby": props.labelId, "aria-label": props.labelStr, className: classNames.safeTiles, children: monthDateProfiles.map((monthDateProfile, i) => { + const monthStr = formatIsoMonthStr(monthDateProfile.currentRange.start); + return (k$1(SingleMonth, { ...props, key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: cssMonthWidth, colCount: cols, isFirst: !i, isLast: i === monthDateProfiles.length - 1, hasLateralSiblings: hasLateralSiblings })); + }) }) }), u$1(Ruler, { widthRef: this.handleInnerWidth })] })) })); + } + // Lifecycle + // ----------------------------------------------------------------------------------------------- + componentDidMount() { + this._isUnmounting = false; + this.scrollState.date = this.props.dateProfile.currentDate; + this.scrollerRef.current.addScrollStartListener(this.handleScrollStart); + this.scrollerRef.current.addScrollEndListener(this.handleScrollEnd); + // this.applyScroll() // definitely not ready yet b/c doesn't have state.innerWidth + // workaround for off-by-a-few-pixels on first time when multiMonthMaxColumns=1, not sure why + setTimeout(() => { + this.applyScroll(); + }, 0); + } + componentDidUpdate(prevProps, prevState) { + if (prevProps.dateProfile !== this.props.dateProfile) { + if (this.context.options.scrollTimeReset) { + this.resetScroll(); + } + else { + this.applyScroll(); + } + } + else if (prevState.innerWidth !== this.state.innerWidth) { + this.applyScroll(); + } + } + componentWillUnmount() { + this._isUnmounting = true; + this.scrollerRef.current.removeScrollStartListener(this.handleScrollStart); + this.scrollerRef.current.removeScrollEndListener(this.handleScrollEnd); + } + resetScroll() { + this.scrollState.date = this.props.dateProfile.currentDate; + this.scrollState.top = undefined; + this.applyScroll(); + } + applyScroll() { + const scroller = this.scrollerRef.current; + const top = this.computeScrollTop(); + if (scroller && top != null) { + scroller.scrollTo({ y: top }); + } + } + computeScrollTop() { + const { scrollState } = this; + if (scrollState.top != null) { + return scrollState.top; + } + if (scrollState.date != null) { + const tilesEl = this.tilesElRef.current; + const monthEl = tilesEl?.querySelector(`[data-date="${formatIsoMonthStr(scrollState.date)}"]`); + const monthWrapEl = monthEl?.parentElement; + if (tilesEl && monthWrapEl) { + // rounding required for proper alignment + const monthTop = Math.round(monthWrapEl.getBoundingClientRect().top); + const originTop = Math.round(tilesEl.getBoundingClientRect().top); + return monthTop - originTop; + } + } + } + } + // date profile + // ------------------------------------------------------------------------------------------------- + const oneMonthDuration = createDuration(1, 'month'); + function splitDateProfileByMonth(dateProfileGenerator, dateProfile, dateEnv, fixedWeekCount, showNonCurrentDates) { + const { start, end } = dateProfile.currentRange; + let monthStart = start; + const monthDateProfiles = []; + while (monthStart.valueOf() < end.valueOf()) { + const monthEnd = dateEnv.add(monthStart, oneMonthDuration); + const currentRange = { + // yuck + start: dateProfileGenerator.skipHiddenDays(monthStart), + end: dateProfileGenerator.skipHiddenDays(monthEnd, -1, true), + }; + let renderRange = buildDayTableRenderRange({ + currentRange, + snapToWeek: true, + fixedWeekCount, + dateEnv, + }); + renderRange = { + // yuck + start: dateProfileGenerator.skipHiddenDays(renderRange.start), + end: dateProfileGenerator.skipHiddenDays(renderRange.end, -1, true), + }; + const activeRange = dateProfile.activeRange ? + intersectRanges(dateProfile.activeRange, showNonCurrentDates ? renderRange : currentRange) : + null; + monthDateProfiles.push({ + currentDate: dateProfile.currentDate, + isValid: dateProfile.isValid, + validRange: dateProfile.validRange, + renderRange, + activeRange, + currentRange, + currentRangeUnit: 'month', + isRangeAllDay: true, + dateIncrement: dateProfile.dateIncrement, + slotMinTime: dateProfile.slotMaxTime, + slotMaxTime: dateProfile.slotMinTime, + }); + monthStart = monthEnd; + } + return monthDateProfiles; + } + // date formatting + // ------------------------------------------------------------------------------------------------- + const YEAR_MONTH_FORMATTER = createFormatter({ year: 'numeric', month: 'long' }); + const YEAR_FORMATTER = createFormatter({ month: 'long' }); + function buildMonthFormat(formatOverride, monthDateProfiles) { + return formatOverride || + ((monthDateProfiles[0].currentRange.start.getUTCFullYear() !== + monthDateProfiles[monthDateProfiles.length - 1].currentRange.start.getUTCFullYear()) + ? YEAR_MONTH_FORMATTER + : YEAR_FORMATTER); + } + + var multiMonthPlugin = { + name: 'multimonth', + initialView: 'multiMonthYear', + views: { + multiMonth: { + component: MultiMonthView, + dateProfileGeneratorClass: TableDateProfileGenerator, + multiMonthMaxColumns: 3, + singleMonthMinWidth: 350, + }, + multiMonthYear: { + type: 'multiMonth', + duration: { years: 1 }, + fixedWeekCount: true, // TODO: apply to all multi-col layouts? + showNonCurrentDates: false, // TODO: looks bad when single-col layout + }, + }, + }; + + var multimonth = /*#__PURE__*/Object.freeze({ + __proto__: null, + 'default': multiMonthPlugin + }); + + const blankButtonState = { + text: '', hint: '', isDisabled: false, + }; + class CalendarController { + constructor(handleDateChange) { + this.handleDateChange = handleDateChange; + } + today() { + this.calendarApi?.today(); + } + prev() { + this.calendarApi?.prev(); + } + next() { + this.calendarApi?.next(); + } + prevYear() { + this.calendarApi?.prevYear(); + } + nextYear() { + this.calendarApi?.nextYear(); + } + gotoDate(zonedDateInput) { + this.calendarApi?.gotoDate(zonedDateInput); + } + incrementDate(duration) { + this.calendarApi?.incrementDate(duration); + } + changeView(viewType) { + this.calendarApi?.changeView(viewType); + } + get view() { + return this.calendarApi?.view; + } + getDate() { + return this.calendarApi?.getDate(); + } + getButtonState() { + const { calendarApi } = this; + return (calendarApi && calendarApi.getButtonState()) || { + today: blankButtonState, + prev: blankButtonState, + next: blankButtonState, + prevYear: blankButtonState, + nextYear: blankButtonState, + }; + } + _setApi(calendarApi) { + if (this.calendarApi !== calendarApi) { + if (this.calendarApi) { + this.calendarApi.off('datesSet', this.handleDateChange); + this.calendarApi = undefined; + } + if (calendarApi) { + this.calendarApi = calendarApi; + calendarApi.on('datesSet', this.handleDateChange); + } + } + } + } + + function formatDate(dateInput, options = {}) { + let dateEnv = buildDateEnv(options); + let formatter = createFormatter(options); + let dateMeta = dateEnv.createMarkerMeta(dateInput); + if (!dateMeta) { // TODO: warning? + return ''; + } + return joinDateTimeFormatParts(dateEnv.formatToParts(dateMeta.marker, formatter)); + } + function formatRange(startInput, endInput, options) { + let dateEnv = buildDateEnv(typeof options === 'object' && options ? options : {}); // pass in if non-null object + let formatter = createFormatter(options); + let startMeta = dateEnv.createMarkerMeta(startInput); + let endMeta = dateEnv.createMarkerMeta(endInput); + if (!startMeta || !endMeta) { // TODO: warning? + return ''; + } + return joinDateTimeFormatParts(dateEnv.formatRangeToParts(startMeta.marker, endMeta.marker, formatter, { + isEndExclusive: options.isEndExclusive, + })); + } + // TODO: more DRY and optimized + function buildDateEnv(settings) { + let locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere + return new DateEnv({ + timeZone: BASE_OPTION_DEFAULTS.timeZone, + calendarSystem: 'gregory', + ...settings, + locale, + }); + } + + /* + if nextDayThreshold is specified, slicing is done in an all-day fashion. + you can get nextDayThreshold from context.nextDayThreshold + */ + function sliceEvents(props, allDay) { + return sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg; + } + + const version = '7.0.1'; + + var protectedStyles = /*#__PURE__*/Object.freeze({ + __proto__: null, + 'default': classNames + }); + + function createRoot(container) { + return { + // eslint-disable-next-line + render: function (children) { + nn(children, container); + }, + // eslint-disable-next-line + unmount: function () { + pn(container); + } + }; + } + + /* + Vanilla JS API + */ + class Calendar$1 extends CalendarApiImpl { + constructor(el, optionOverrides = {}) { + super(); + this.baseId = `fc:${guid()}:`; + this.isRendering = false; + this.isRendered = false; + this.customContentRenderId = 0; + this.currentClassName = ''; + this.currentColorScheme = ''; + this.handleDataChange = (data, actions) => { + this.currentData = data; + let renderImmediate = false; + for (const action of actions) { + if (action.type === 'SET_EVENT_DRAG' || + action.type === 'UNSET_EVENT_DRAG' || + action.type === 'SET_EVENT_RESIZE' || + action.type === 'UNSET_EVENT_RESIZE' || + // could happen as a result of a drag or resize and must be part of same sync pipeline + action.type === 'MERGE_EVENTS') { + renderImmediate = true; + break; + } + } + this.renderRunner.request(renderImmediate ? undefined : data.calendarOptions.rerenderDelay); + }; + this.handleRenderRequest = () => { + if (this.isRendering) { + let { currentData } = this; + this.isRendered = true; + bn(() => { + this.vdomRoot.render(u$1(S, { children: u$1(RenderId.Provider, { value: this.customContentRenderId, children: u$1(CalendarMediaRoot, { emitter: currentData.emitter, children: (forPrint) => { + const options = currentData.calendarOptions; + const isRtl = options.direction === 'rtl'; + const className = computeRootClassName(options, forPrint); + this.setIsRtl(isRtl); + this.setClassName(className); + this.setHeight(options.height); + this.setColorScheme(options.colorScheme || ''); + return (u$1(CalendarInner, { ...currentData, forPrint: forPrint, baseId: this.baseId })); + } }) }) })); + }); + } + else if (this.isRendered) { + this.isRendered = false; + this.vdomRoot.unmount(); + this.setIsRtl(false); + this.setClassName(''); + this.setHeight(''); + this.setColorScheme(''); + } + }; + this.el = el; + this.vdomRoot = createRoot(el); + this.renderRunner = new DelayedRunner(this.handleRenderRequest); + this.dataManager = new CalendarDataManager({ + calendarApi: this, + onDataChange: this.handleDataChange, + }); + this.currentData = this.dataManager.update(optionOverrides); + } + render() { + let wasRendering = this.isRendering; + if (!wasRendering) { + this.isRendering = true; + } + else { + this.customContentRenderId += 1; + } + this.renderRunner.request(); + } + destroy() { + if (this.isRendering) { + this.isRendering = false; + this.renderRunner.request(); + } + this.dataManager.destroy(); + } + batchRendering(func) { + this.renderRunner.pause('batchRendering'); + func(); + this.renderRunner.resume('batchRendering'); + } + pauseRendering() { + this.renderRunner.pause('pauseRendering'); + } + resumeRendering() { + this.renderRunner.resume('pauseRendering', true); + } + resetOptions(optionOverrides, changedOptionNames) { + this.currentDataManager.resetOptions(optionOverrides, changedOptionNames); + } + setClassName(className) { + if (className !== this.currentClassName) { + let { classList } = this.el; + for (let singleClassName of this.currentClassName.split(' ')) { + if (singleClassName) { + classList.remove(singleClassName); + } + } + for (let singleClassName of className.split(' ')) { + if (singleClassName) { + classList.add(singleClassName); + } + } + this.currentClassName = className; + } + } + setHeight(height) { + applyStyleProp(this.el, 'height', height); + } + setColorScheme(colorScheme) { + if (colorScheme !== this.currentColorScheme) { + if (colorScheme) { + this.el.dataset.colorScheme = colorScheme; + } + else { + delete this.el.dataset.colorScheme; + } + this.currentColorScheme = colorScheme; + } + } + setIsRtl(isRtl) { + if (isRtl) { + this.el.dir = 'rtl'; + } + else { + this.el.removeAttribute('dir'); + } + } + } + + const plugins = [ + interactionPlugin, + dayGridPlugin, + timeGridPlugin, + listPlugin, + multiMonthPlugin, + ]; + class Calendar extends Calendar$1 { + constructor(el, optionOverrides = {}) { + super(el, { + ...optionOverrides, + plugins: [ + ...plugins, + ...(optionOverrides.plugins || []), + ] + }); + } + } + + const Shared = { F: globalLocales, G: globalPlugins, H: joinClassNames, S, u: u$1 }; + + exports.Calendar = Calendar; + exports.CalendarController = CalendarController; + exports.DayGrid = daygrid; + exports.Interaction = interaction; + exports.JsonRequestError = JsonRequestError; + exports.List = list; + exports.MultiMonth = multimonth; + exports.Preact = preact; + exports.PreactJSXRuntime = jsxRuntime; + exports.ProtectedApi = protectedApi; + exports.ProtectedStyles = protectedStyles; + exports.Shared = Shared; + exports.TimeGrid = timegrid; + exports.formatDate = formatDate; + exports.formatRange = formatRange; + exports.globalLocales = globalLocales; + exports.globalPlugins = globalPlugins; + exports.joinClassNames = joinClassNames; + exports.sliceEvents = sliceEvents; + exports.version = version; + + Object.defineProperty(exports, '__esModule', { value: true }); + + return exports; + +})({}); diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/locales-all/global.js b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/locales-all/global.js new file mode 100644 index 0000000..359a7de --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/locales-all/global.js @@ -0,0 +1,1728 @@ +/*! +FullCalendar (Vanilla JS) v7.0.1 +Docs & License: https://fullcalendar.io +(c) 2026 Adam Shaw +*/ +(function ({ F: globalLocales }) { + + + var __zh_tw$l78 = { + code: 'zh-tw', + prevText: '上個', + nextText: '下個', + todayText: '今天', + yearText: '年', + monthText: '月', + weekTextLong: '週', + dayText: '天', + listText: '活動列表', + allDayText: '整天', + moreLinkText: '顯示更多', + noEventsText: '沒有任何活動', + }; + + var __zh_cn$l77 = { + code: 'zh-cn', + week: { + // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: '上月', + nextText: '下月', + todayText: '今天', + yearText: '年', + monthText: '月', + weekTextLong: '周', + dayText: '日', + listText: '日程', + allDayText: '全天', + moreLinkText(n) { + return '另外 ' + n + ' 个'; + }, + noEventsText: '没有事件显示', + }; + + var __vi$l76 = { + code: 'vi', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Trước', + nextText: 'Tiếp', + todayText: 'Hôm nay', + yearText: 'Năm', + monthText: 'Tháng', + weekTextLong: 'Tuần', + weekTextShort: 'Tu', + dayText: 'Ngày', + listText: 'Lịch biểu', + allDayText: 'Cả ngày', + moreLinkText(n) { + return '+ thêm ' + n; + }, + noEventsText: 'Không có sự kiện để hiển thị', + }; + + var __uz$l75 = { + code: 'uz', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Oldingi', + nextText: 'Keyingi', + todayText: 'Bugun', + yearText: 'Yil', + monthText: 'Oy', + weekTextLong: 'Xafta', + dayText: 'Kun', + listText: 'Kun tartibi', + allDayText: 'Kun bo\'yi', + moreLinkText(n) { + return '+ yana ' + n; + }, + noEventsText: 'Ko\'rsatish uchun voqealar yo\'q', + }; + + var __uz_cy$l74 = { + code: 'uz-cy', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Олин', + nextText: 'Кейин', + todayText: 'Бугун', + monthText: 'Ой', + weekTextLong: 'Ҳафта', + dayText: 'Кун', + listText: 'Кун тартиби', + allDayText: 'Кун\nбўйича', + moreLinkText(n) { + return '+ яна ' + n; + }, + noEventsText: 'Кўрсатиш учун воқеалар йўқ', + }; + + var __uk$l73 = { + code: 'uk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'Попередній', + nextText: 'далі', + todayText: 'Сьогодні', + yearText: 'рік', + monthText: 'Місяць', + weekTextLong: 'Тиждень', + weekTextShort: 'Тиж', + dayText: 'День', + listText: 'Порядок денний', + allDayText: 'Увесь\nдень', + moreLinkText(n) { + return '+ще ' + n + '...'; + }, + noEventsText: 'Немає подій для відображення', + }; + + var __ug$l72 = { + code: 'ug', + prevText: 'ئالدىنقى', + nextText: 'كېيىنكى', + todayText: 'بۈگۈن', + yearText: 'يىل', + monthText: 'ئاي', + weekTextLong: 'ھەپتە', + dayText: 'كۈن', + listText: 'كۈنتەرتىپ', + allDayText: 'پۈتۈن كۈن', + }; + + var __tr$l71 = { + code: 'tr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'geri', + nextText: 'ileri', + todayText: 'bugün', + yearText: 'Yıl', + monthText: 'Ay', + weekTextLong: 'Hafta', + weekTextShort: 'Hf', + dayText: 'Gün', + listText: 'Ajanda', + allDayText: 'Tüm gün', + moreLinkText: 'daha fazla', + noEventsText: 'Gösterilecek etkinlik yok', + }; + + var __th$l70 = { + code: 'th', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'ก่อนหน้า', + nextText: 'ถัดไป', + prevYearText: 'ปีก่อนหน้า', + nextYearText: 'ปีถัดไป', + yearText: 'ปี', + todayText: 'วันนี้', + monthText: 'เดือน', + weekTextLong: 'สัปดาห์', + dayText: 'วัน', + listText: 'กำหนดการ', + allDayText: 'ตลอดวัน', + moreLinkText: 'เพิ่มเติม', + noEventsText: 'ไม่มีกิจกรรมที่จะแสดง', + }; + + var __ta_in$l69 = { + code: 'ta-in', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'முந்தைய', + nextText: 'அடுத்தது', + todayText: 'இன்று', + yearText: 'ஆண்டு', + monthText: 'மாதம்', + weekTextLong: 'வாரம்', + dayText: 'நாள்', + listText: 'தினசரி அட்டவணை', + allDayText: 'நாள்\nமுழுவதும்', + moreLinkText(n) { + return '+ மேலும் ' + n; + }, + noEventsText: 'காண்பிக்க நிகழ்வுகள் இல்லை', + }; + + var __sv$l68 = { + code: 'sv', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Förra', + nextText: 'Nästa', + todayText: 'Idag', + yearText: 'År', + monthText: 'Månad', + weekTextLong: 'Vecka', + weekTextShort: 'v.', + dayText: 'Dag', + listText: 'Program', + prevHint(unitText) { + return `Föregående ${unitText.toLocaleLowerCase()}`; + }, + nextHint(unitText) { + return `Nästa ${unitText.toLocaleLowerCase()}`; + }, + todayHint(unitText, unit) { + return (unit === 'year' ? 'I' : 'Denna') + ' ' + unitText.toLocaleLowerCase(); + }, + viewHint: '$0 vy', + navLinkHint: 'Gå till $0', + moreLinkHint(eventCnt) { + return `Visa ytterligare ${eventCnt} händelse${eventCnt === 1 ? '' : 'r'}`; + }, + allDayText: 'Heldag', + moreLinkText: 'till', + noEventsText: 'Inga händelser att visa', + closeHint: 'Stäng', + eventsHint: 'Händelser', + }; + + var __sr$l67 = { + code: 'sr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'Prethodna', + nextText: 'Sledeći', + todayText: 'Danas', + yearText: 'Godina', + monthText: 'Mеsеc', + weekTextLong: 'Nеdеlja', + weekTextShort: 'Sed', + dayText: 'Dan', + listText: 'Planеr', + allDayText: 'Cеo dan', + moreLinkText(n) { + return '+ još ' + n; + }, + noEventsText: 'Nеma događaja za prikaz', + }; + + var __sr_cyrl$l66 = { + code: 'sr-cyrl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'Претходна', + nextText: 'следећи', + todayText: 'Данас', + yearText: 'Година', + monthText: 'Месец', + weekTextLong: 'Недеља', + weekTextShort: 'Сед', + dayText: 'Дан', + listText: 'Планер', + allDayText: 'Цео дан', + moreLinkText(n) { + return '+ још ' + n; + }, + noEventsText: 'Нема догађаја за приказ', + }; + + var __sq$l65 = { + code: 'sq', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'mbrapa', + nextText: 'Përpara', + todayText: 'Sot', + yearText: 'Viti', + monthText: 'Muaj', + weekTextLong: 'Javë', + weekTextShort: 'Ja', + dayText: 'Ditë', + listText: 'Listë', + allDayText: 'Gjithë\nditën', + moreLinkText(n) { + return '+më tepër ' + n; + }, + noEventsText: 'Nuk ka evente për të shfaqur', + }; + + var __sm$l64 = { + code: 'sm', + prevText: 'Talu ai', + nextText: 'Mulimuli atu', + todayText: 'Aso nei', + yearText: 'Tausaga', + monthText: 'Masina', + weekTextLong: 'Vaiaso', + dayText: 'Aso', + listText: 'Faasologa', + allDayText: 'Aso atoa', + moreLinkText: 'sili atu', + noEventsText: 'Leai ni mea na tutupu', + }; + + var __sl$l63 = { + code: 'sl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'Prejšnji', + nextText: 'Naslednji', + todayText: 'Trenutni', + yearText: 'Leto', + monthText: 'Mesec', + weekTextLong: 'Teden', + dayText: 'Dan', + listText: 'Dnevni red', + allDayText: 'Ves dan', + moreLinkText: 'več', + noEventsText: 'Ni dogodkov za prikaz', + }; + + var __sk$l62 = { + code: 'sk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Predchádzajúci', + nextText: 'Nasledujúci', + todayText: 'Dnes', + yearText: 'Rok', + monthText: 'Mesiac', + weekTextLong: 'Týždeň', + weekTextShort: 'Ty', + dayText: 'Deň', + listText: 'Rozvrh', + allDayText: 'Celý deň', + moreLinkText(n) { + return '+ďalšie: ' + n; + }, + noEventsText: 'Žiadne akcie na zobrazenie', + }; + + var __si_lk$l61 = { + code: 'si-lk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'පෙර', + nextText: 'පසු', + todayText: 'අද', + yearText: 'අවුරුදු', + monthText: 'මාසය', + weekTextLong: 'සතිය', + weekTextShort: 'සති', + dayText: 'දවස', + listText: 'ලැයිස්තුව', + allDayText: 'සියලු', + moreLinkText: 'තවත්', + noEventsText: 'මුකුත් නැත', + }; + + var __ru$l60 = { + code: 'ru', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Пред', + nextText: 'След', + todayText: 'Сегодня', + yearText: 'Год', + monthText: 'Месяц', + weekTextLong: 'Неделя', + weekTextShort: 'Нед', + dayText: 'День', + listText: 'Повестка дня', + allDayText: 'Весь\nдень', + moreLinkText(n) { + return '+ ещё ' + n; + }, + noEventsText: 'Нет событий для отображения', + }; + + var __ro$l59 = { + code: 'ro', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'precedentă', + nextText: 'următoare', + todayText: 'Azi', + yearText: 'An', + monthText: 'Lună', + weekTextLong: 'Săptămână', + weekTextShort: 'Săpt', + dayText: 'Zi', + listText: 'Agendă', + allDayText: 'Toată\nziua', + moreLinkText(n) { + return '+alte ' + n; + }, + noEventsText: 'Nu există evenimente de afișat', + }; + + var __pt$l58 = { + code: 'pt', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Anterior', + nextText: 'Seguinte', + todayText: 'Hoje', + yearText: 'Ano', + monthText: 'Mês', + weekTextLong: 'Semana', + weekTextShort: 'Sem', + dayText: 'Dia', + listText: 'Agenda', + allDayText: 'Todo\no dia', + moreLinkText: 'mais', + noEventsText: 'Não há eventos para mostrar', + }; + + var __pt_br$l57 = { + code: 'pt-br', + prevText: 'Anterior', + nextText: 'Próximo', + prevYearText: 'Ano anterior', + nextYearText: 'Próximo ano', + yearText: 'Ano', + todayText: 'Hoje', + monthText: 'Mês', + weekTextLong: 'Semana', + weekTextShort: 'Sm', + dayText: 'Dia', + listText: 'Lista', + prevHint: '$0 Anterior', + nextHint: 'Próximo $0', + todayHint(unitText, unit) { + return (unit === 'day') ? 'Hoje' : + ((unit === 'week') ? 'Esta' : 'Este') + ' ' + unitText.toLocaleLowerCase(); + }, + viewHint(unitText, unit) { + return 'Visualizar ' + (unit === 'week' ? 'a' : 'o') + ' ' + unitText.toLocaleLowerCase(); + }, + allDayText: 'Dia\ninteiro', + moreLinkText(n) { + return 'mais +' + n; + }, + moreLinkHint(eventCnt) { + return `Mostrar mais ${eventCnt} eventos`; + }, + noEventsText: 'Não há eventos para mostrar', + navLinkHint: 'Ir para $0', + closeHint: 'Fechar', + eventsHint: 'Eventos', + }; + + var __pl$l56 = { + code: 'pl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Poprzedni', + nextText: 'Następny', + todayText: 'Dziś', + yearText: 'Rok', + monthText: 'Miesiąc', + weekTextLong: 'Tydzień', + weekTextShort: 'Tydz', + dayText: 'Dzień', + listText: 'Plan dnia', + allDayText: 'Cały\ndzień', + moreLinkText: 'więcej', + noEventsText: 'Brak wydarzeń do wyświetlenia', + }; + + var __nn$l55 = { + code: 'nn', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Førre', + nextText: 'Neste', + todayText: 'I dag', + yearText: 'År', + monthText: 'Månad', + weekTextLong: 'Veke', + dayText: 'Dag', + listText: 'Agenda', + allDayText: 'Heile\ndagen', + moreLinkText: 'til', + noEventsText: 'Ingen hendelser å vise', + }; + + var __nl$l54 = { + code: 'nl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Vorige', + nextText: 'Volgende', + todayText: 'Vandaag', + yearText: 'Jaar', + monthText: 'Maand', + weekTextLong: 'Week', + dayText: 'Dag', + listText: 'Lijst', + allDayText: 'Hele dag', + moreLinkText: 'extra', + noEventsText: 'Geen evenementen om te laten zien', + }; + + var __ne$l53 = { + code: 'ne', // code for nepal + week: { + dow: 7, // Sunday is the first day of the week. + doy: 1, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'अघिल्लो', + nextText: 'अर्को', + todayText: 'आज', + yearText: 'वर्ष', + monthText: 'महिना', + weekTextLong: 'हप्ता', + dayText: 'दिन', + listText: 'सूची', + allDayText: 'दिनभरि', + moreLinkText: 'थप लिंक', + noEventsText: 'देखाउनको लागि कुनै घटनाहरू छैनन्', + }; + + var __nb$l52 = { + code: 'nb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Forrige', + nextText: 'Neste', + todayText: 'I dag', + yearText: 'År', + monthText: 'Måned', + weekTextLong: 'Uke', + dayText: 'Dag', + listText: 'Agenda', + allDayText: 'Hele\ndagen', + moreLinkText: 'til', + noEventsText: 'Ingen hendelser å vise', + prevHint: 'Forrige $0', + nextHint: 'Neste $0', + todayHint: 'Nåværende $0', + viewHint: '$0 visning', + navLinkHint: 'Gå til $0', + moreLinkHint(eventCnt) { + return `Vis ${eventCnt} flere hendelse${eventCnt === 1 ? '' : 'r'}`; + }, + }; + + var __ms$l51 = { + code: 'ms', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'Sebelum', + nextText: 'Selepas', + todayText: 'hari ini', + yearText: 'Tahun', + monthText: 'Bulan', + weekTextLong: 'Minggu', + weekTextShort: 'Mg', + dayText: 'Hari', + listText: 'Agenda', + allDayText: 'Sepanjang\nhari', + moreLinkText(n) { + return 'masih ada ' + n + ' acara'; + }, + noEventsText: 'Tiada peristiwa untuk dipaparkan', + }; + + var __mk$l50 = { + code: 'mk', + prevText: 'претходно', + nextText: 'следно', + todayText: 'Денес', + yearText: 'година', + monthText: 'Месец', + weekTextLong: 'Недела', + weekTextShort: 'Сед', + dayText: 'Ден', + listText: 'График', + allDayText: 'Цел ден', + moreLinkText(n) { + return '+повеќе ' + n; + }, + noEventsText: 'Нема настани за прикажување', + }; + + var __lv$l49 = { + code: 'lv', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Iepr.', + nextText: 'Nāk.', + todayText: 'Šodien', + yearText: 'Gads', + monthText: 'Mēnesis', + weekTextLong: 'Nedēļa', + weekTextShort: 'Ned.', + dayText: 'Diena', + listText: 'Dienas kārtība', + allDayText: 'Visu\ndienu', + moreLinkText(n) { + return '+vēl ' + n; + }, + noEventsText: 'Nav notikumu', + }; + + var __lt$l48 = { + code: 'lt', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Atgal', + nextText: 'Pirmyn', + todayText: 'Šiandien', + yearText: 'Metai', + monthText: 'Mėnuo', + weekTextLong: 'Savaitė', + weekTextShort: 'SAV', + dayText: 'Diena', + listText: 'Darbotvarkė', + allDayText: 'Visą\ndieną', + moreLinkText: 'daugiau', + noEventsText: 'Nėra įvykių rodyti', + }; + + var __lb$l47 = { + code: 'lb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Zréck', + nextText: 'Weider', + todayText: 'Haut', + yearText: 'Joer', + monthText: 'Mount', + weekTextLong: 'Woch', + weekTextShort: 'W', + dayText: 'Dag', + listText: 'Terminiwwersiicht', + allDayText: 'Ganzen\nDag', + moreLinkText: 'méi', + noEventsText: 'Nee Evenementer ze affichéieren', + }; + + var __ku$l46 = { + code: 'ku', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + prevText: 'پێشتر', + nextText: 'دواتر', + todayText: 'ئەمڕو', + yearText: 'ساڵ', + monthText: 'مانگ', + weekTextLong: 'هەفتە', + dayText: 'ڕۆژ', + listText: 'بەرنامە', + allDayText: 'هەموو ڕۆژەکە', + moreLinkText: 'زیاتر', + noEventsText: 'هیچ ڕووداوێك نیە', + }; + + var __ko$l45 = { + code: 'ko', + prevText: '이전달', + nextText: '다음달', + todayText: '오늘', + yearText: '년도', + monthText: '월', + weekTextLong: '주', + dayText: '일', + listText: '일정목록', + allDayText: '종일', + moreLinkText: '개', + noEventsText: '일정이 없습니다', + }; + + var __km$l44 = { + code: 'km', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'មុន', + nextText: 'បន្ទាប់', + todayText: 'ថ្ងៃនេះ', + yearText: 'ឆ្នាំ', + monthText: 'ខែ', + weekTextLong: 'សប្តាហ៍', + dayText: 'ថ្ងៃ', + listText: 'បញ្ជី', + allDayText: 'ពេញមួយថ្ងៃ', + moreLinkText: 'ច្រើនទៀត', + noEventsText: 'គ្មានព្រឹត្តិការណ៍ត្រូវបង្ហាញ', + }; + + var __kk$l43 = { + code: 'kk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'Алдыңғы', + nextText: 'Келесі', + todayText: 'Бүгін', + yearText: 'Жыл', + monthText: 'Ай', + weekTextLong: 'Апта', + dayText: 'Күн', + listText: 'Күн тәртібі', + allDayText: 'Күні\nбойы', + moreLinkText(n) { + return '+ тағы ' + n; + }, + noEventsText: 'Көрсету үшін оқиғалар жоқ', + }; + + var __ka$l42 = { + code: 'ka', + week: { + dow: 1, + doy: 7, + }, + prevText: 'წინა', + nextText: 'შემდეგი', + todayText: 'დღეს', + yearText: 'წელიწადი', + monthText: 'თვე', + weekTextLong: 'კვირა', + weekTextShort: 'კვ', + dayText: 'დღე', + listText: 'დღის წესრიგი', + allDayText: 'მთელი\nდღე', + moreLinkText(n) { + return '+ კიდევ ' + n; + }, + noEventsText: 'ღონისძიებები არ არის', + }; + + var __ja$l41 = { + code: 'ja', + prevText: '前', + nextText: '次', + todayText: '今日', + yearText: '年', + monthText: '月', + weekTextLong: '週', + dayText: '日', + listText: '予定リスト', + allDayText: '終日', + moreLinkText(n) { + return '他 ' + n + ' 件'; + }, + noEventsText: '表示する予定はありません', + }; + + var __it$l40 = { + code: 'it', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Prec', + nextText: 'Succ', + todayText: 'Oggi', + yearText: 'Anno', + monthText: 'Mese', + weekTextLong: 'Settimana', + weekTextShort: 'Sm', + dayText: 'Giorno', + listText: 'Agenda', + allDayText: 'Tutto\nil giorno', + moreLinkText(n) { + return '+altri ' + n; + }, + noEventsText: 'Non ci sono eventi da visualizzare', + }; + + var __is$l39 = { + code: 'is', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Fyrri', + nextText: 'Næsti', + todayText: 'Í dag', + yearText: 'Ár', + monthText: 'Mánuður', + weekTextLong: 'Vika', + dayText: 'Dagur', + listText: 'Dagskrá', + allDayText: 'Allan\ndaginn', + moreLinkText: 'meira', + noEventsText: 'Engir viðburðir til að sýna', + }; + + var __id$l38 = { + code: 'id', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'mundur', + nextText: 'maju', + todayText: 'hari ini', + yearText: 'Tahun', + monthText: 'Bulan', + weekTextLong: 'Minggu', + weekTextShort: 'Mg', + dayText: 'Hari', + listText: 'Agenda', + allDayText: 'Sehari\npenuh', + moreLinkText: 'lebih', + noEventsText: 'Tidak ada acara untuk ditampilkan', + }; + + var __hy_am$l37 = { + code: 'hy-am', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Նախորդ', + nextText: 'Հաջորդ', + todayText: 'Այսօր', + yearText: 'Տարի', + monthText: 'Ամիս', + weekTextLong: 'Շաբաթ', + weekTextShort: 'Շաբ', + dayText: 'Օր', + listText: 'Օրվա ցուցակ', + allDayText: 'Ամբողջ օր', + moreLinkText(n) { + return '+ ևս ' + n; + }, + noEventsText: 'Բացակայում է իրադարձությունը ցուցադրելու', + }; + + var __hu$l36 = { + code: 'hu', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Vissza', + nextText: 'Előre', + todayText: 'Ma', + yearText: 'Év', + monthText: 'Hónap', + weekTextLong: 'Hét', + dayText: 'Nap', + listText: 'Lista', + allDayText: 'Egész\nnap', + moreLinkText: 'további', + noEventsText: 'Nincs megjeleníthető esemény', + }; + + var __hr$l35 = { + code: 'hr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'Prijašnji', + nextText: 'Sljedeći', + todayText: 'Danas', + yearText: 'Godina', + monthText: 'Mjesec', + weekTextLong: 'Tjedan', + weekTextShort: 'Tje', + dayText: 'Dan', + listText: 'Raspored', + allDayText: 'Cijeli\ndan', + moreLinkText(n) { + return '+ još ' + n; + }, + noEventsText: 'Nema događaja za prikaz', + }; + + var __hi$l34 = { + code: 'hi', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'पिछला', + nextText: 'अगला', + todayText: 'आज', + yearText: 'वर्ष', + monthText: 'महीना', + weekTextLong: 'सप्ताह', + weekTextShort: 'हफ्ता', + dayText: 'दिन', + listText: 'कार्यसूची', + allDayText: 'सभी दिन', + moreLinkText(n) { + return '+अधिक ' + n; + }, + noEventsText: 'कोई घटनाओं को प्रदर्शित करने के लिए', + }; + + var __he$l33 = { + code: 'he', + direction: 'rtl', + prevText: 'הקודם', + nextText: 'הבא', + todayText: 'היום', + yearText: 'שנה', + monthText: 'חודש', + weekTextLong: 'שבוע', + dayText: 'יום', + listText: 'סדר יום', + allDayText: 'כל היום', + moreLinkText: 'נוספים', + noEventsText: 'אין אירועים להצגה', + }; + + var __gl$l32 = { + code: 'gl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Ant', + nextText: 'Seg', + todayText: 'Hoxe', + yearText: 'Ano', + monthText: 'Mes', + weekTextLong: 'Semana', + weekTextShort: 'Sm', + dayText: 'Día', + listText: 'Axenda', + prevHint: '$0 antes', + nextHint: '$0 seguinte', + todayHint(unitText, unit) { + return (unit === 'day') ? 'Hoxe' : + ((unit === 'week') ? 'Esta' : 'Este') + ' ' + unitText.toLocaleLowerCase(); + }, + viewHint(unitText, unit) { + return 'Vista ' + (unit === 'week' ? 'da' : 'do') + ' ' + unitText.toLocaleLowerCase(); + }, + allDayText: 'Todo\no día', + moreLinkText: 'máis', + moreLinkHint(eventCnt) { + return `Amosar ${eventCnt} eventos máis`; + }, + noEventsText: 'Non hai eventos para amosar', + navLinkHint: 'Ir ao $0', + closeHint: 'Pechar', + eventsHint: 'Eventos', + }; + + var __fr$l31 = { + code: 'fr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Précédent', + nextText: 'Suivant', + todayText: 'Aujourd\'hui', + yearText: 'Année', + monthText: 'Mois', + weekTextLong: 'Semaine', + weekTextShort: 'Sem.', + dayText: 'Jour', + listText: 'Planning', + allDayText: 'Toute la\njournée', + moreLinkText: 'en plus', + noEventsText: 'Aucun évènement à afficher', + }; + + var __fr_ch$l30 = { + code: 'fr-ch', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Précédent', + nextText: 'Suivant', + todayText: 'Courant', + yearText: 'Année', + monthText: 'Mois', + weekTextLong: 'Semaine', + weekTextShort: 'Sm', + dayText: 'Jour', + listText: 'Mon planning', + allDayText: 'Toute la\njournée', + moreLinkText: 'en plus', + noEventsText: 'Aucun évènement à afficher', + }; + + var __fr_ca$l29 = { + code: 'fr', + prevText: 'Précédent', + nextText: 'Suivant', + todayText: 'Aujourd\'hui', + yearText: 'Année', + monthText: 'Mois', + weekTextLong: 'Semaine', + weekTextShort: 'Sem.', + dayText: 'Jour', + listText: 'Mon planning', + allDayText: 'Toute la\njournée', + moreLinkText: 'en plus', + noEventsText: 'Aucun évènement à afficher', + }; + + var __fi$l28 = { + code: 'fi', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Edellinen', + nextText: 'Seuraava', + todayText: 'Tänään', + yearText: 'Vuosi', + monthText: 'Kuukausi', + weekTextLong: 'Viikko', + weekTextShort: 'Vk', + dayText: 'Päivä', + listText: 'Tapahtumat', + allDayText: 'Koko\npäivä', + moreLinkText: 'lisää', + noEventsText: 'Ei näytettäviä tapahtumia', + }; + + var __fa$l27 = { + code: 'fa', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + prevText: 'قبلی', + nextText: 'بعدی', + todayText: 'امروز', + yearText: 'سال', + monthText: 'ماه', + weekTextLong: 'هفته', + weekTextShort: 'هف', + dayText: 'روز', + listText: 'برنامه', + allDayText: 'تمام روز', + moreLinkText(n) { + return 'بیش از ' + n; + }, + noEventsText: 'هیچ رویدادی به نمایش', + }; + + var __eu$l26 = { + code: 'eu', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'Aur', + nextText: 'Hur', + todayText: 'Gaur', + yearText: 'Urtea', + monthText: 'Hilabetea', + weekTextLong: 'Astea', + weekTextShort: 'As', + dayText: 'Eguna', + listText: 'Agenda', + allDayText: 'Egun\nosoa', + moreLinkText: 'gehiago', + noEventsText: 'Ez dago ekitaldirik erakusteko', + }; + + var __et$l25 = { + code: 'et', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Eelnev', + nextText: 'Järgnev', + todayText: 'Täna', + yearText: 'Aasta', + monthText: 'Kuu', + weekTextLong: 'Nädal', + weekTextShort: 'Näd', + dayText: 'Päev', + listText: 'Päevakord', + allDayText: 'Kogu\npäev', + moreLinkText(n) { + return '+ veel ' + n; + }, + noEventsText: 'Kuvamiseks puuduvad sündmused', + }; + + var __es$l24 = { + code: 'es', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Ant', + nextText: 'Sig', + todayText: 'Hoy', + yearText: 'Año', + monthText: 'Mes', + weekTextLong: 'Semana', + weekTextShort: 'Sm', + dayText: 'Día', + listText: 'Agenda', + prevHint: '$0 antes', + nextHint: '$0 siguiente', + todayHint(unitText, unit) { + return (unit === 'day') ? 'Hoy' : + ((unit === 'week') ? 'Esta' : 'Este') + ' ' + unitText.toLocaleLowerCase(); + }, + viewHint(unitText, unit) { + return 'Vista ' + (unit === 'week' ? 'de la' : 'del') + ' ' + unitText.toLocaleLowerCase(); + }, + allDayText: 'Todo\nel día', + moreLinkText: 'más', + moreLinkHint(eventCnt) { + return `Mostrar ${eventCnt} eventos más`; + }, + noEventsText: 'No hay eventos para mostrar', + navLinkHint: 'Ir al $0', + closeHint: 'Cerrar', + eventsHint: 'Eventos', + }; + + var __es_us$l23 = { + code: 'es', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'Ant', + nextText: 'Sig', + todayText: 'Hoy', + yearText: 'Año', + monthText: 'Mes', + weekTextLong: 'Semana', + weekTextShort: 'Sm', + dayText: 'Día', + listText: 'Agenda', + prevHint: '$0 antes', + nextHint: '$0 siguiente', + todayHint(unitText, unit) { + return (unit === 'day') ? 'Hoy' : + ((unit === 'week') ? 'Esta' : 'Este') + ' ' + unitText.toLocaleLowerCase(); + }, + viewHint(unitText, unit) { + return 'Vista ' + (unit === 'week' ? 'de la' : 'del') + ' ' + unitText.toLocaleLowerCase(); + }, + allDayText: 'Todo\nel día', + moreLinkText: 'más', + noEventsText: 'No hay eventos para mostrar', + }; + + var __eo$l22 = { + code: 'eo', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Antaŭa', + nextText: 'Sekva', + todayText: 'Hodiaŭ', + yearText: 'Jaro', + monthText: 'Monato', + weekTextLong: 'Semajno', + weekTextShort: 'Sm', + dayText: 'Tago', + listText: 'Tagordo', + allDayText: 'Tuta\ntago', + moreLinkText: 'pli', + noEventsText: 'Neniuj eventoj por montri', + }; + + var __en_nz$l21 = { + code: 'en-nz', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + weekTextShort: 'W', + todayHint: 'This $0', + prevHint: 'Previous $0', + nextHint: 'Next $0', + viewHint: '$0 view', + navLinkHint: 'Go to $0', + moreLinkHint(eventCnt) { + return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`; + }, + }; + + var __en_gb$l20 = { + code: 'en-gb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + weekTextShort: 'W', + todayHint: 'This $0', + prevHint: 'Previous $0', + nextHint: 'Next $0', + viewHint: '$0 view', + navLinkHint: 'Go to $0', + moreLinkHint(eventCnt) { + return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`; + }, + }; + + var __en_au$l19 = { + code: 'en-au', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + weekTextShort: 'W', + todayHint: 'This $0', + prevHint: 'Previous $0', + nextHint: 'Next $0', + viewHint: '$0 view', + navLinkHint: 'Go to $0', + moreLinkHint(eventCnt) { + return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`; + }, + }; + + var __el$l18 = { + code: 'el', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4st is the first week of the year. + }, + prevText: 'Προηγούμενος', + nextText: 'Επόμενος', + todayText: 'Σήμερα', + yearText: 'Ετος', + monthText: 'Μήνας', + weekTextLong: 'Εβδομάδα', + weekTextShort: 'Εβδ', + dayText: 'Ημέρα', + listText: 'Ατζέντα', + allDayText: 'Ολοήμερο', + moreLinkText: 'περισσότερα', + noEventsText: 'Δεν υπάρχουν γεγονότα προς εμφάνιση', + }; + + function __de$affix(unitText) { + return (unitText === 'Tag' || unitText === 'Monat') ? 'r' : + unitText === 'Jahr' ? 's' : ''; + } + var __de$l17 = { + code: 'de', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Zurück', + nextText: 'Vor', + todayText: 'Heute', + yearText: 'Jahr', + monthText: 'Monat', + weekTextLong: 'Woche', + weekTextShort: 'KW', + dayText: 'Tag', + listText: 'Terminübersicht', + allDayText: 'Ganztägig', + moreLinkText(n) { + return '+ weitere ' + n; + }, + noEventsText: 'Keine Ereignisse anzuzeigen', + prevHint(unitText) { + return `Vorherige${__de$affix(unitText)} ${unitText}`; + }, + nextHint(unitText) { + return `Nächste${__de$affix(unitText)} ${unitText}`; + }, + todayHint(unitText) { + // → Heute, Diese Woche, Dieser Monat, Dieses Jahr + if (unitText === 'Tag') { + return 'Heute'; + } + return `Diese${__de$affix(unitText)} ${unitText}`; + }, + viewHint(unitText) { + // → Tagesansicht, Wochenansicht, Monatsansicht, Jahresansicht + const glue = unitText === 'Woche' ? 'n' : unitText === 'Monat' ? 's' : 'es'; + return unitText + glue + 'ansicht'; + }, + navLinkHint: 'Gehe zu $0', + moreLinkHint(eventCnt) { + return 'Zeige ' + (eventCnt === 1 ? + 'ein weiteres Ereignis' : + eventCnt + ' weitere Ereignisse'); + }, + closeHint: 'Schließen', + eventsHint: 'Ereignisse', + }; + + function __de_at$affix(unitText) { + return (unitText === 'Tag' || unitText === 'Monat') ? 'r' : + unitText === 'Jahr' ? 's' : ''; + } + var __de_at$l16 = { + code: 'de-at', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Zurück', + nextText: 'Vor', + todayText: 'Heute', + yearText: 'Jahr', + monthText: 'Monat', + weekTextLong: 'Woche', + weekTextShort: 'KW', + dayText: 'Tag', + listText: 'Terminübersicht', + allDayText: 'Ganztägig', + moreLinkText(n) { + return '+ weitere ' + n; + }, + noEventsText: 'Keine Ereignisse anzuzeigen', + prevHint(unitText) { + return `Vorherige${__de_at$affix(unitText)} ${unitText}`; + }, + nextHint(unitText) { + return `Nächste${__de_at$affix(unitText)} ${unitText}`; + }, + todayHint(unitText) { + // → Heute, Diese Woche, Dieser Monat, Dieses Jahr + if (unitText === 'Tag') { + return 'Heute'; + } + return `Diese${__de_at$affix(unitText)} ${unitText}`; + }, + viewHint(unitText) { + // → Tagesansicht, Wochenansicht, Monatsansicht, Jahresansicht + const glue = unitText === 'Woche' ? 'n' : unitText === 'Monat' ? 's' : 'es'; + return unitText + glue + 'ansicht'; + }, + navLinkHint: 'Gehe zu $0', + moreLinkHint(eventCnt) { + return 'Zeige ' + (eventCnt === 1 ? + 'ein weiteres Ereignis' : + eventCnt + ' weitere Ereignisse'); + }, + closeHint: 'Schließen', + eventsHint: 'Ereignisse', + }; + + var __da$l15 = { + code: 'da', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Forrige', + nextText: 'Næste', + todayText: 'I dag', + yearText: 'År', + monthText: 'Måned', + weekTextLong: 'Uge', + dayText: 'Dag', + listText: 'Agenda', + allDayText: 'Hele\ndagen', + moreLinkText: 'flere', + noEventsText: 'Ingen arrangementer at vise', + }; + + var __cy$l14 = { + code: 'cy', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Blaenorol', + nextText: 'Nesaf', + todayText: 'Heddiw', + yearText: 'Blwyddyn', + monthText: 'Mis', + weekTextLong: 'Wythnos', + dayText: 'Dydd', + listText: 'Rhestr', + allDayText: 'Trwy\'r\ndydd', + moreLinkText: 'Mwy', + noEventsText: 'Dim digwyddiadau', + }; + + var __cs$l13 = { + code: 'cs', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Dříve', + nextText: 'Později', + todayText: 'Nyní', + yearText: 'Rok', + monthText: 'Měsíc', + weekTextLong: 'Týden', + weekTextShort: 'Týd', + dayText: 'Den', + listText: 'Agenda', + allDayText: 'Celý den', + moreLinkText(n) { + return '+další: ' + n; + }, + noEventsText: 'Žádné akce k zobrazení', + }; + + var __ca$l12 = { + code: 'ca', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Anterior', + nextText: 'Següent', + todayText: 'Avui', + yearText: 'Any', + monthText: 'Mes', + weekTextLong: 'Setmana', + weekTextShort: 'Set', + dayText: 'Dia', + listText: 'Agenda', + allDayText: 'Tot\nel dia', + moreLinkText: 'més', + noEventsText: 'No hi ha esdeveniments per mostrar', + }; + + var __bs$l11 = { + code: 'bs', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'Prošli', + nextText: 'Sljedeći', + todayText: 'Danas', + yearText: 'Godina', + monthText: 'Mjesec', + weekTextLong: 'Sedmica', + weekTextShort: 'Sed', + dayText: 'Dan', + listText: 'Raspored', + allDayText: 'Cijeli\ndan', + moreLinkText(n) { + return '+ još ' + n; + }, + noEventsText: 'Nema događaja za prikazivanje', + }; + + var __bn$l10 = { + code: 'bn', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + prevText: 'পেছনে', + nextText: 'সামনে', + todayText: 'আজ', + yearText: 'বছর', + monthText: 'মাস', + weekTextLong: 'সপ্তাহ', + dayText: 'দিন', + listText: 'তালিকা', + allDayText: 'সারাদিন', + moreLinkText(n) { + return '+অন্যান্য ' + n; + }, + noEventsText: 'কোনো ইভেন্ট নেই', + }; + + var __bg$l9 = { + code: 'bg', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'назад', + nextText: 'напред', + todayText: 'днес', + yearText: 'година', + monthText: 'Месец', + weekTextLong: 'Седмица', + dayText: 'Ден', + listText: 'График', + allDayText: 'Цял ден', + moreLinkText(n) { + return '+още ' + n; + }, + noEventsText: 'Няма събития за показване', + }; + + var __az$l8 = { + code: 'az', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + prevText: 'Əvvəl', + nextText: 'Sonra', + todayText: 'Bu Gün', + yearText: 'Il', + monthText: 'Ay', + weekTextLong: 'Həftə', + dayText: 'Gün', + listText: 'Gündəm', + allDayText: 'Bütün\nGün', + moreLinkText(n) { + return '+ daha çox ' + n; + }, + noEventsText: 'Göstərmək üçün hadisə yoxdur', + }; + + var __ar$l7 = { + code: 'ar', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + prevText: 'السابق', + nextText: 'التالي', + todayText: 'اليوم', + yearText: 'سنة', + monthText: 'شهر', + weekTextLong: 'أسبوع', + dayText: 'يوم', + listText: 'أجندة', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var __ar_tn$l6 = { + code: 'ar-tn', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + direction: 'rtl', + prevText: 'السابق', + nextText: 'التالي', + todayText: 'اليوم', + yearText: 'سنة', + monthText: 'شهر', + weekTextLong: 'أسبوع', + dayText: 'يوم', + listText: 'أجندة', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var __ar_sa$l5 = { + code: 'ar-sa', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + prevText: 'السابق', + nextText: 'التالي', + todayText: 'اليوم', + yearText: 'سنة', + monthText: 'شهر', + weekTextLong: 'أسبوع', + dayText: 'يوم', + listText: 'أجندة', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var __ar_ma$l4 = { + code: 'ar-ma', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + prevText: 'السابق', + nextText: 'التالي', + todayText: 'اليوم', + yearText: 'سنة', + monthText: 'شهر', + weekTextLong: 'أسبوع', + dayText: 'يوم', + listText: 'أجندة', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var __ar_ly$l3 = { + code: 'ar-ly', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + prevText: 'السابق', + nextText: 'التالي', + todayText: 'اليوم', + yearText: 'سنة', + monthText: 'شهر', + weekTextLong: 'أسبوع', + dayText: 'يوم', + listText: 'أجندة', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var __ar_kw$l2 = { + code: 'ar-kw', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + prevText: 'السابق', + nextText: 'التالي', + todayText: 'اليوم', + yearText: 'سنة', + monthText: 'شهر', + weekTextLong: 'أسبوع', + dayText: 'يوم', + listText: 'أجندة', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var __ar_dz$l1 = { + code: 'ar-dz', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 4, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + prevText: 'السابق', + nextText: 'التالي', + todayText: 'اليوم', + yearText: 'سنة', + monthText: 'شهر', + weekTextLong: 'أسبوع', + dayText: 'يوم', + listText: 'أجندة', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var __af$l0 = { + code: 'af', + week: { + dow: 1, // Maandag is die eerste dag van die week. + doy: 4, // Die week wat die 4de Januarie bevat is die eerste week van die jaar. + }, + prevText: 'Vorige', + nextText: 'Volgende', + todayText: 'Vandag', + yearText: 'Jaar', + monthText: 'Maand', + weekTextLong: 'Week', + dayText: 'Dag', + listText: 'Agenda', + allDayText: 'Heeldag', + moreLinkText: 'Addisionele', + noEventsText: 'Daar is geen gebeurtenisse nie', + }; + + globalLocales.push( + __af$l0, __ar_dz$l1, __ar_kw$l2, __ar_ly$l3, __ar_ma$l4, __ar_sa$l5, __ar_tn$l6, __ar$l7, __az$l8, __bg$l9, __bn$l10, __bs$l11, __ca$l12, __cs$l13, __cy$l14, __da$l15, __de_at$l16, __de$l17, __el$l18, __en_au$l19, __en_gb$l20, __en_nz$l21, __eo$l22, __es_us$l23, __es$l24, __et$l25, __eu$l26, __fa$l27, __fi$l28, __fr_ca$l29, __fr_ch$l30, __fr$l31, __gl$l32, __he$l33, __hi$l34, __hr$l35, __hu$l36, __hy_am$l37, __id$l38, __is$l39, __it$l40, __ja$l41, __ka$l42, __kk$l43, __km$l44, __ko$l45, __ku$l46, __lb$l47, __lt$l48, __lv$l49, __mk$l50, __ms$l51, __nb$l52, __ne$l53, __nl$l54, __nn$l55, __pl$l56, __pt_br$l57, __pt$l58, __ro$l59, __ru$l60, __si_lk$l61, __sk$l62, __sl$l63, __sm$l64, __sq$l65, __sr_cyrl$l66, __sr$l67, __sv$l68, __ta_in$l69, __th$l70, __tr$l71, __ug$l72, __uk$l73, __uz_cy$l74, __uz$l75, __vi$l76, __zh_cn$l77, __zh_tw$l78, + ); + +})(FullCalendar.Shared); diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/skeleton.css b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/skeleton.css new file mode 100644 index 0000000..3643334 --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/skeleton.css @@ -0,0 +1,469 @@ + +:root { + --fc-sticky-header-footer-z: 3; + --fc-popover-z: 4; +} + +.fc-11 { + z-index: var(--fc-popover-z) !important; +} + +.fc-HY { + isolation: isolate; +} + +/* +This will prevent all ancestors from customizing their box size unless they use .contentBox +*/ +.fc-jz, +.fc-jz *, +.fc-jz *:before, +.fc-jz *:after { + box-sizing: border-box !important; +} + +/* classes attached to */ +.fc-j2, +.fc-j2 * { + cursor: not-allowed !important; +} + +.fc-Ki { + -ms-overflow-style: none !important; /* IE and Edge */ + scrollbar-width: none !important; /* Firefox */ +} + +/* Hide scrollbar for Chrome, Safari and Opera */ +.fc-Ki::-webkit-scrollbar { + display: none !important; +} + +.fc-ST { + flex-shrink: 0 !important; +} + +.fc-Z9 .fc-1g { + display: flex !important; + flex-direction: row !important; + flex-wrap: wrap !important; +} + +/* HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard: + https://stackoverflow.com/a/60256345 */ +.fc-HC .fc-1g > * { + float: left !important; +} + +[dir=rtl] .fc-HC .fc-1g > *, +.fc-HC[dir=rtl] .fc-1g > * { + float: right !important; +} + +.fc-HC .fc-1g::after { + content: "" !important; + display: block !important; + clear: both !important; +} + +.fc-ZB { + cursor: pointer !important; +} + +.fc-Ld { + cursor: n-resize !important; +} + +.fc-lJ { + cursor: s-resize !important; +} + +.fc-Zm { + cursor: w-resize !important; +} + +[dir=rtl] .fc-Zm { + cursor: e-resize !important; +} + +.fc-n5 { + cursor: e-resize !important; +} + +[dir=rtl] .fc-n5 { + cursor: w-resize !important; +} + +.fc-Px { + cursor: col-resize !important; +} + +.fc-QK, +.fc-eh, +.fc-M9 { + position: absolute !important; + box-sizing: content-box !important; + width: 100% !important; + height: 100% !important; +} + +.fc-QK { + padding: 10px !important; + margin: -10px !important; +} + +.fc-eh { + padding-left: 10px !important; + padding-right: 10px !important; + margin-left: -10px !important; + margin-right: -10px !important; +} + +.fc-M9 { + padding-top: 10px !important; + padding-bottom: 10px !important; + margin-top: -10px !important; + margin-bottom: -10px !important; +} + +/* QUESTION: why not use -left -right instead of negative margins/padding for ALL? */ +.fc-SC { + position: absolute; + top: 0; + bottom: 0; + left: -5px; + right: -5px; +} + +.fc-hi { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.fc-F0 { + visibility: hidden; +} + +/* Border Utils */ +/* ------------------------------------------------------------------------------------------------- */ + +/* TODO: break-out to x and y. don't have "only" all */ +/* TODO: instead of "border" utils, call them "borderless" to subtract? */ + +.fc-6s { + border: 0 !important; +} + +.fc-8B { + border-left: 0 !important; + border-right: 0 !important; + border-bottom: 0 !important; +} + +.fc-RJ { + border-top: 0 !important; + border-left: 0 !important; + border-right: 0 !important; +} + +.fc-Hm, +.fc-ge { + border-top: 0 !important; + border-bottom: 0 !important; +} + +.fc-Hm { + border-inline-end: 0 !important; +} + +.fc-ge { + border-inline-start: 0 !important; +} + +.fc-KC { + border-left: 0 !important; + border-right: 0 !important; +} + +.fc-mE { + border-top: 0 !important; + border-bottom: 0 !important; +} + +/* for matching cell start-border, assumed to be 1px, which can't be guaranteed */ +.fc-FA { + border-inline-start: 1px solid transparent !important; +} + +/* Flexbox Utils */ +/* ------------------------------------------------------------------------------------------------- */ + +.fc-5g { + display: flex !important; + flex-direction: row !important; +} + +.fc-Fa { + display: flex !important; + flex-direction: column !important; +} + +.fc-16 { + flex-grow: 1 !important; +} + +.fc-Im { + flex-grow: 1 !important; + flex-basis: 0 !important; + min-width: 0 !important; + min-height: 0 !important; +} + +.fc-tZ { + min-height: 0 !important; +} + +/* +TODO: use liquidX/Y elsewhere because frees up min other dimension; less collisions +*/ +.fc-Pw { + flex-grow: 1 !important; + flex-basis: 0 !important; + min-width: 0 !important; +} + +/* Print-Safe Utils (media:screen ONLY) */ +/* ------------------------------------------------------------------------------------------------- */ + +.fc-Z9 .fc-kE, +.fc-Z9 .fc-Xg { + display: flex !important; + flex-direction: column !important; +} + +/* Table Utils */ +/* ------------------------------------------------------------------------------------------------- */ + +.fc-np { + padding: 0 !important; +} + +.fc-gd { + margin: 0 !important; +} + +.fc-hB { + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +.fc-uk { + margin-left: 0 !important; + margin-right: 0 !important; +} + +.fc-he { + white-space: nowrap !important; +} + +.fc-Bv { + white-space: pre !important; +} + +/* Misc Utils */ +/* ------------------------------------------------------------------------------------------------- */ + +.fc-U2 { + overflow-anchor: none; +} + +.fc-bB { + overflow: hidden !important; +} + +/* +TODO: eventually use this on daygrid/timegrid events' inner, time, and title, FOR PRINT +Needed to prevent wrapping to multiple lines, increasing height, throwing off print-positioning, +which can't rely on dynamic height detection after print-flag activated. +*/ +.fc-pp { + white-space: nowrap !important; + overflow: hidden !important; +} + +.fc-8A { + position: relative !important; +} + +.fc-7t { + position: absolute !important; +} + +.fc-pK { + inset-inline-start: 0 !important; +} + +.fc-wP { + position: absolute !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; +} + +.fc-yW, +.fc-5j { + position: absolute !important; + left: 0 !important; + right: 0 !important; +} + +.fc-1K, +.fc-bH { + position: absolute !important; + top: 0 !important; + bottom: 0 !important; +} + +.fc-yW { + top: 0 !important; +} + +.fc-bH { + left: 0 !important; + right: 0 !important; + width: 0 !important; +} + +@media not print { + .fc-dY { + position: sticky !important; + } + + .fc-aX { + position: sticky !important; + top: 0 !important; + } + + /* Sticks to either left or right (the flex "start") depending on ltr/rtl */ + .fc-pZ { + position: sticky !important; + inset-inline-start: 0 !important; + } + + .fc-Mw { + position: sticky !important; + top: 0 !important; + z-index: var(--fc-sticky-header-footer-z) !important; + } +} + +/* Only needed when padding/border must be separate from width/height */ +.fc-CU { + box-sizing: content-box !important; +} + +.fc-jm { + position: absolute !important; + left: -10000px !important; +} + +.fc-oO { + align-items: center !important; +} + +.fc-MM { + align-items: flex-start !important; +} + +.fc-Qk { + align-items: flex-end !important; +} + +/* Footer Scrollbar */ +/* ------------------------------------------------------------------------------------------------- */ + +.fc-Q6 { + position: sticky !important; + bottom: 0 !important; + z-index: var(--fc-sticky-header-footer-z) !important; +} + +.fc-bD > * { + margin-top: -1px !important; +} + +.fc-bD > * > * { + height: 1px !important; +} + +/* Print-Safe Utils */ +/* ------------------------------------------------------------------------------------------------- */ + +.fc-HC .fc-lF { + -moz-column-break-inside: avoid !important; + break-inside: avoid !important; +} + +.fc-HC .fc-kE { + display: table !important; + table-layout: fixed !important; + width: 100% !important; + border-spacing: 0 !important; + border-collapse: separate !important; +} + +.fc-HC .fc-Xg { + display: table-header-group !important; + -moz-column-break-inside: avoid !important; + break-inside: avoid !important; + background: #fff !important; + z-index: 9999 !important; + position: relative !important; +} + +.fc-Vf { + /* min-height when multiple rows coexist, provides sane aspect-ratio for cells */ + min-height: 6em !important; +} + +/* Z-index */ +/* ------------------------------------------------------------------------------------------------- */ + +.fc-BP { + z-index: 0; +} + +.fc-XV { + z-index: 1; +} + +.fc-Ah:focus-visible { + z-index: 2; +} + +/* INTERNAL MARKER -- used to mark an element for internal significance */ +/* ------------------------------------------------------------------------------------------------- */ + +.fc-vz {} + +.fc-4B {} +.fc-D7 {} +.fc-wp {} +.fc-Ex {} +.fc-wl {} +.fc-yM {} +.fc-2f {} +.fc-zR {} + +.fc-0j {} + +.fc-M7 {} + +.fc-tb {} + +.fc-ew {} + +.fc-IZ {} + +.fc-UH {} diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/global.js b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/global.js new file mode 100644 index 0000000..a971b8f --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/global.js @@ -0,0 +1,387 @@ +/*! +FullCalendar (Vanilla JS) v7.0.1 +Docs & License: https://fullcalendar.io +(c) 2026 Adam Shaw +*/ +(function ({ H: joinClassNames, u, S, G: globalPlugins }) { + + + // usually 11px font / 12px line-height + const xxsTextClass = "fc-breezy-vQz"; + // outline + const outlineWidthClass = "fc-breezy-0Bj"; + const outlineWidthFocusClass = "fc-breezy-uqo"; + const outlineWidthGroupFocusClass = "fc-breezy-nPR"; + const outlineOffsetClass = "fc-breezy-3Xj"; + const primaryOutlineColorClass = "fc-breezy-yg7"; + const primaryOutlineFocusClass = `${outlineWidthFocusClass} ${primaryOutlineColorClass}`; + // neutral buttons + const strongSolidPressableClass = joinClassNames("fc-breezy-hJa", "fc-breezy-AT7", "fc-breezy-b3B"); + const mutedHoverClass = "fc-breezy-qiG"; + const mutedHoverPressableClass = `${mutedHoverClass} fc-breezy-WfX`; + const faintHoverClass = "fc-breezy-1EL"; + const faintHoverPressableClass = `${faintHoverClass} fc-breezy-qWF fc-breezy-UTk`; + // controls + const selectedClass = `fc-breezy-GfU fc-breezy-Oiq ${primaryOutlineFocusClass}`; + const unselectedClass = `fc-breezy-t4l fc-breezy-hnE ${primaryOutlineFocusClass}`; + // primary + const primaryClass = "fc-breezy-Anp fc-breezy-ECg"; + const primaryPressableClass = `${primaryClass} fc-breezy-8mf`; + const primaryPressableGroupClass = `${primaryClass} fc-breezy-t8l`; + const primaryButtonClass = `${primaryPressableClass} fc-breezy-d0j ${primaryOutlineFocusClass} ${outlineOffsetClass}`; + // secondary + const secondaryClass = "fc-breezy-nyY fc-breezy-NK3"; + const secondaryPressableClass = `${secondaryClass} fc-breezy-StK`; + const secondaryButtonClass = `${secondaryPressableClass} fc-breezy-nuP ${primaryOutlineFocusClass} fc-breezy-07j`; + const secondaryButtonIconClass = "fc-breezy-XUJ fc-breezy-J7Y fc-breezy-b7A fc-breezy-tRE"; + // event content + const eventMutedFgClass = "fc-breezy-WLU"; + const eventFaintBgClass = "fc-breezy-GjO"; + const eventFaintPressableClass = joinClassNames(eventFaintBgClass, "fc-breezy-yE8", "fc-breezy-x2T"); + // interactive neutral foregrounds + const mutedFgPressableGroupClass = "fc-breezy-t4l fc-breezy-Wxh fc-breezy-x7E"; + // transparent resizer for mouse + const blockPointerResizerClass = "fc-breezy-1EY fc-breezy-pps fc-breezy-vs6"; + const rowPointerResizerClass = `${blockPointerResizerClass} fc-breezy-AWB fc-breezy-hza`; + const columnPointerResizerClass = `${blockPointerResizerClass} fc-breezy-MaV fc-breezy-uuA`; + // circle resizer for touch + const blockTouchResizerClass = "fc-breezy-1EY fc-breezy-3wQ fc-breezy-wsy fc-breezy-lNM fc-breezy-gmc fc-breezy-AAA"; + const rowTouchResizerClass = `${blockTouchResizerClass} fc-breezy-ERR fc-breezy-Dq8`; + const columnTouchResizerClass = `${blockTouchResizerClass} fc-breezy-1V6 fc-breezy-F99`; + const getNormalDayHeaderBorderClass = (info) => joinClassNames(!info.inPopover && (info.isMajor ? "fc-breezy-wsy fc-breezy-OFc" : + !info.isNarrow && "fc-breezy-wsy fc-breezy-EAo")); + const getMutedDayHeaderBorderClass = (info) => joinClassNames(!info.inPopover && (info.isMajor ? "fc-breezy-wsy fc-breezy-OFc" : + !info.isNarrow && "fc-breezy-wsy fc-breezy-tTN")); + const getNormalDayCellBorderColorClass = (info) => (info.isMajor ? "fc-breezy-OFc" : "fc-breezy-EAo"); + const getMutedDayCellBorderColorClass = (info) => (info.isMajor ? "fc-breezy-OFc" : "fc-breezy-tTN"); + const tallDayCellBottomClass = "fc-breezy-mhE"; + const getShortDayCellBottomClass = (info) => joinClassNames(!info.isNarrow && "fc-breezy-toR"); + const mutedHoverButtonClass = joinClassNames(mutedHoverPressableClass, outlineWidthFocusClass, primaryOutlineColorClass); + const dayRowCommonClasses = { + /* Day Row > List-Item Event + ----------------------------------------------------------------------------------------------- */ + listItemEventClass: (info) => joinClassNames("fc-breezy-Ika fc-breezy-7A6", info.isNarrow + ? "fc-breezy-148 fc-breezy-Fvv" + : "fc-breezy-rVY fc-breezy-KzJ", info.isSelected + ? "fc-breezy-pZQ" + : info.isInteractive + ? mutedHoverPressableClass + : mutedHoverClass), + listItemEventInnerClass: (info) => joinClassNames("fc-breezy-dl1 fc-breezy-1sP fc-breezy-XpK fc-breezy-N2M", info.isNarrow + ? `fc-breezy-z5u ${xxsTextClass}` + : "fc-breezy-2rx fc-breezy-a3B"), + listItemEventTimeClass: (info) => joinClassNames(info.isNarrow ? "fc-breezy-F1o" : "fc-breezy-oQ2", "fc-breezy-t4l fc-breezy-NPw fc-breezy-TZ4 fc-breezy-pKG fc-breezy-1Zl"), + listItemEventTitleClass: (info) => joinClassNames(info.isNarrow ? "fc-breezy-F1o" : "fc-breezy-oQ2", "fc-breezy-Oiq fc-breezy-1OT fc-breezy-TZ4 fc-breezy-pKG fc-breezy-OLq", info.timeText && "fc-breezy-IPx"), + /* Day Row > Row Event + ----------------------------------------------------------------------------------------------- */ + rowEventClass: (info) => joinClassNames(info.isStart && (info.isNarrow ? "fc-breezy-Jzj" : "fc-breezy-Wga"), info.isEnd && (info.isNarrow ? "fc-breezy-3e1" : "fc-breezy-KYn")), + rowEventInnerClass: (info) => info.isNarrow ? "fc-breezy-z5u" : "fc-breezy-2rx", + /* Day Row > More-Link + ----------------------------------------------------------------------------------------------- */ + rowMoreLinkClass: (info) => joinClassNames("fc-breezy-Ika fc-breezy-wsy", info.isNarrow + ? "fc-breezy-148 fc-breezy-UIT fc-breezy-Fvv" + : "fc-breezy-sI7 fc-breezy-rVY fc-breezy-d0j fc-breezy-KzJ", mutedHoverPressableClass), + rowMoreLinkInnerClass: (info) => joinClassNames(info.isNarrow + ? `fc-breezy-7A6 ${xxsTextClass}` + : "fc-breezy-KUX fc-breezy-a3B", "fc-breezy-Oiq"), + }; + var index = { + name: "theme-breezy", + optionDefaults: { + className: (info) => joinClassNames("fc-breezy-gmc fc-breezy-n5m", !(info.borderlessTop || info.borderlessBottom || info.borderlessX) && "fc-breezy-hny"), + viewClass: (info) => { + const hasBorderTop = !info.options.headerToolbar && !info.borderlessTop; + const hasBorderBottom = !info.options.footerToolbar && !info.borderlessBottom; + const hasBorderX = !info.borderlessX; + return joinClassNames("fc-breezy-EAo", hasBorderTop && "fc-breezy-ku3", hasBorderBottom && "fc-breezy-zi1", hasBorderX && "fc-breezy-1Wx", (hasBorderTop && hasBorderX) && "fc-breezy-wko", (hasBorderBottom && hasBorderX) && "fc-breezy-L2o", !info.isHeightAuto && "fc-breezy-pKG"); + }, + /* Toolbar + --------------------------------------------------------------------------------------------- */ + toolbarClass: (info) => joinClassNames("fc-breezy-KRz fc-breezy-OEz fc-breezy-nYK fc-breezy-dl1 fc-breezy-1sP fc-breezy-dNl fc-breezy-XpK fc-breezy-N2M fc-breezy-Pms fc-breezy-pKG fc-breezy-EAo", !info.borderlessX && "fc-breezy-1Wx"), + headerToolbarClass: (info) => joinClassNames("fc-breezy-zi1", !info.borderlessTop && "fc-breezy-ku3", !(info.borderlessTop || info.borderlessX) && "fc-breezy-wko"), + footerToolbarClass: (info) => joinClassNames("fc-breezy-ku3", !info.borderlessBottom && "fc-breezy-zi1", !(info.borderlessBottom || info.borderlessX) && "fc-breezy-L2o"), + toolbarSectionClass: "fc-breezy-yi0 fc-breezy-dl1 fc-breezy-1sP fc-breezy-XpK fc-breezy-Pms", + toolbarTitleClass: "fc-breezy-9ZS fc-breezy-C8a fc-breezy-Oiq", + buttonGroupClass: (info) => joinClassNames("fc-breezy-dl1 fc-breezy-1sP fc-breezy-XpK", !info.hasSelection && "fc-breezy-KzJ fc-breezy-eSM"), + buttonClass: (info) => joinClassNames("fc-breezy-bCs fc-breezy-dl6 fc-breezy-dl1 fc-breezy-1sP fc-breezy-XpK fc-breezy-9yp fc-breezy-Z9U", info.isIconOnly ? "fc-breezy-Nca" : "fc-breezy-Apf", info.buttonGroup?.hasSelection ? joinClassNames("fc-breezy-KzJ fc-breezy-1OT", info.isSelected + ? selectedClass + : unselectedClass) : joinClassNames("fc-breezy-C8a", info.isPrimary + ? primaryButtonClass + : secondaryButtonClass, info.buttonGroup + ? "fc-breezy-Ps8 fc-breezy-H1W fc-breezy-g3A fc-breezy-7ss fc-breezy-JIC" + : "fc-breezy-KzJ fc-breezy-eSM fc-breezy-wsy")), + buttons: { + prev: { + iconContent: () => chevronDown(joinClassNames(secondaryButtonIconClass, "fc-breezy-z44 fc-breezy-keW")), + }, + next: { + iconContent: () => chevronDown(joinClassNames(secondaryButtonIconClass, "fc-breezy-KxI fc-breezy-ZW3")), + }, + prevYear: { + iconContent: () => chevronDoubleLeft(joinClassNames(secondaryButtonIconClass, "fc-breezy-asP")) + }, + nextYear: { + iconContent: () => chevronDoubleLeft(joinClassNames(secondaryButtonIconClass, "fc-breezy-jmT fc-breezy-jY6")) + }, + }, + /* Abstract Event + --------------------------------------------------------------------------------------------- */ + eventShortHeight: 50, + eventColor: "var(--fc-breezy-event)", + eventContrastColor: "var(--fc-breezy-event-contrast)", + eventClass: (info) => joinClassNames(info.isDragging && "fc-breezy-n5m", info.event.url && "fc-breezy-JiE", info.isSelected + ? joinClassNames(outlineWidthClass, info.isDragging && "fc-breezy-tkw") + : outlineWidthFocusClass, primaryOutlineColorClass), + /* Background Event + --------------------------------------------------------------------------------------------- */ + backgroundEventColor: "var(--fc-breezy-background-event)", + backgroundEventClass: "fc-breezy-aPk fc-breezy-jsy fc-breezy-DO7", + backgroundEventTitleClass: (info) => joinClassNames("fc-breezy-lMo fc-breezy-L1Y", info.isNarrow + ? `fc-breezy-iS4 ${xxsTextClass}` + : "fc-breezy-3N5 fc-breezy-a3B", "fc-breezy-sI1"), + /* Block Event + --------------------------------------------------------------------------------------------- */ + blockEventClass: (info) => joinClassNames("fc-breezy-bCs fc-breezy-eYX fc-breezy-vwH fc-breezy-d0j fc-breezy-DO7", info.isInteractive ? eventFaintPressableClass : eventFaintBgClass, (info.isDragging && !info.isSelected) && "fc-breezy-iTG"), + blockEventInnerClass: eventMutedFgClass, + blockEventTimeClass: "fc-breezy-TZ4 fc-breezy-pKG fc-breezy-1Zl", + blockEventTitleClass: "fc-breezy-TZ4 fc-breezy-pKG fc-breezy-OLq", + /* Row Event + --------------------------------------------------------------------------------------------- */ + rowEventClass: (info) => joinClassNames("fc-breezy-Ika fc-breezy-JIC", info.isStart && joinClassNames("fc-breezy-3J4", info.isNarrow ? "fc-breezy-kmj" : "fc-breezy-QUg"), info.isEnd && joinClassNames("fc-breezy-USt", info.isNarrow ? "fc-breezy-Skl" : "fc-breezy-RNO")), + rowEventBeforeClass: (info) => joinClassNames(info.isStartResizable && joinClassNames(info.isSelected ? rowTouchResizerClass : rowPointerResizerClass, "fc-breezy-11a")), + rowEventAfterClass: (info) => joinClassNames(info.isEndResizable && joinClassNames(info.isSelected ? rowTouchResizerClass : rowPointerResizerClass, "fc-breezy-Tuc")), + rowEventInnerClass: (info) => joinClassNames("fc-breezy-dl1 fc-breezy-1sP fc-breezy-XpK", info.isNarrow ? xxsTextClass : "fc-breezy-a3B"), + rowEventTimeClass: (info) => joinClassNames(info.isNarrow ? "fc-breezy-a7i" : "fc-breezy-C2j", "fc-breezy-1OT"), + rowEventTitleClass: (info) => (info.isNarrow ? "fc-breezy-oQ2" : "fc-breezy-aCI"), + /* Column Event + --------------------------------------------------------------------------------------------- */ + columnEventClass: (info) => joinClassNames("fc-breezy-1Wx fc-breezy-A3h fc-breezy-9Iz", info.isStart && joinClassNames("fc-breezy-ku3 fc-breezy-wko", info.isNarrow ? "fc-breezy-sEX" : "fc-breezy-zZM"), info.isEnd && joinClassNames("fc-breezy-zi1 fc-breezy-L2o", info.isNarrow ? "fc-breezy-Ika" : "fc-breezy-vUo")), + columnEventBeforeClass: (info) => joinClassNames(info.isStartResizable && joinClassNames(info.isSelected ? columnTouchResizerClass : columnPointerResizerClass, "fc-breezy-YDC")), + columnEventAfterClass: (info) => joinClassNames(info.isEndResizable && joinClassNames(info.isSelected ? columnTouchResizerClass : columnPointerResizerClass, "fc-breezy-fJL")), + columnEventInnerClass: (info) => joinClassNames("fc-breezy-dl1", info.isShort + ? "fc-breezy-1sP fc-breezy-XpK fc-breezy-NWN fc-breezy-iS4" + : joinClassNames("fc-breezy-sgX", info.isNarrow ? "fc-breezy-aCI fc-breezy-2rx" : "fc-breezy-Nca fc-breezy-Jhn"), (info.isShort || info.isNarrow) ? xxsTextClass : "fc-breezy-a3B"), + columnEventTimeClass: (info) => (!info.isShort && (info.isNarrow ? "fc-breezy-166" : "fc-breezy-4dx")), + columnEventTitleClass: (info) => joinClassNames(!info.isShort && (info.isNarrow ? "fc-breezy-2rx" : "fc-breezy-Jhn"), "fc-breezy-C8a"), + /* More-Link + --------------------------------------------------------------------------------------------- */ + moreLinkClass: `${outlineWidthFocusClass} ${primaryOutlineColorClass}`, + moreLinkInnerClass: "fc-breezy-TZ4 fc-breezy-pKG", + columnMoreLinkClass: (info) => joinClassNames(info.isNarrow ? "fc-breezy-SEP" : "fc-breezy-V9v", `fc-breezy-wsy fc-breezy-d0j fc-breezy-4MR fc-breezy-KzJ ${strongSolidPressableClass} fc-breezy-vwH fc-breezy-A3h fc-breezy-9Iz`), + columnMoreLinkInnerClass: (info) => joinClassNames(info.isNarrow + ? `fc-breezy-KUX ${xxsTextClass}` + : "fc-breezy-iS4 fc-breezy-a3B", "fc-breezy-sI1"), + /* Day Header + --------------------------------------------------------------------------------------------- */ + dayHeaderAlign: (info) => info.inPopover ? "start" : "center", + dayHeaderClass: (info) => joinClassNames("fc-breezy-E9P", info.inPopover && "fc-breezy-zi1 fc-breezy-EAo fc-breezy-nYK"), + dayHeaderInnerClass: (info) => joinClassNames("fc-breezy-dl1 fc-breezy-1sP fc-breezy-XpK", (!info.dayNumberText && !info.inPopover) + ? joinClassNames("fc-breezy-Jhn fc-breezy-Fvv fc-breezy-a3B", info.isNarrow + ? "fc-breezy-aCI fc-breezy-gMS fc-breezy-t4l" + : "fc-breezy-ZrE fc-breezy-bvX fc-breezy-C8a fc-breezy-sI1", info.hasNavLink && mutedHoverButtonClass) + : (info.isToday && info.dayNumberText && !info.inPopover) + ? joinClassNames("fc-breezy-bCs fc-breezy-bvX fc-breezy-hS8", info.isNarrow ? "fc-breezy-TFV" : "fc-breezy-I1A") + : joinClassNames("fc-breezy-Fvv", info.inPopover + ? "fc-breezy-bvX fc-breezy-aCI fc-breezy-2rx" + : joinClassNames("fc-breezy-fn8 fc-breezy-TFV fc-breezy-ZrE", info.isNarrow ? "fc-breezy-2tF" : "fc-breezy-X6C"), info.hasNavLink && mutedHoverButtonClass)), + dayHeaderContent: (info) => ((!info.dayNumberText && !info.inPopover) ? (u(S, { children: info.text })) : (u(S, { children: info.textParts.map((textPart, i) => (u("span", { className: joinClassNames("fc-breezy-jm6", info.isNarrow ? "fc-breezy-a3B" : "fc-breezy-9yp", textPart.type === "day" + ? joinClassNames("fc-breezy-dl1 fc-breezy-1sP fc-breezy-XpK", !info.isNarrow && "fc-breezy-C8a", (info.isToday && !info.inPopover) + ? joinClassNames("fc-breezy-cKZ fc-breezy-AAA fc-breezy-E9P", info.isNarrow ? "fc-breezy-MSG" : "fc-breezy-n6w", info.hasNavLink + ? `${primaryPressableGroupClass} ${outlineWidthGroupFocusClass} ${outlineOffsetClass} ${primaryOutlineColorClass}` + : primaryClass) + : "fc-breezy-Oiq") + : "fc-breezy-t4l"), children: textPart.value }, i))) }))), + /* Day Cell + --------------------------------------------------------------------------------------------- */ + dayCellClass: (info) => joinClassNames("fc-breezy-wsy", ((info.isOther || info.isDisabled) && !info.options.businessHours) && "fc-breezy-nYK"), + dayCellTopClass: (info) => joinClassNames(info.isNarrow ? "fc-breezy-84e" : "fc-breezy-p7s", "fc-breezy-dl1 fc-breezy-1sP"), + dayCellTopInnerClass: (info) => joinClassNames("fc-breezy-dl1 fc-breezy-1sP fc-breezy-XpK fc-breezy-E9P fc-breezy-TZ4", info.isNarrow + ? `fc-breezy-SEP fc-breezy-oM6 ${xxsTextClass}` + : "fc-breezy-V9v fc-breezy-TFV fc-breezy-a3B", info.isToday + ? joinClassNames("fc-breezy-AAA fc-breezy-C8a", info.isNarrow ? "fc-breezy-qvL" : "fc-breezy-Wga", info.text === info.dayNumberText + ? (info.isNarrow ? "fc-breezy-79F" : "fc-breezy-ilz") + : (info.isNarrow ? "fc-breezy-aCI" : "fc-breezy-Nca"), info.hasNavLink + ? `${primaryPressableClass} ${outlineOffsetClass}` + : primaryClass) + : joinClassNames("fc-breezy-Skl", info.isNarrow ? "fc-breezy-aCI" : "fc-breezy-Nca", info.hasNavLink && mutedHoverPressableClass, info.isOther + ? "fc-breezy-mNS" + : (info.monthText ? "fc-breezy-sI1" : "fc-breezy-t4l"), info.monthText && "fc-breezy-DIS")), + dayCellInnerClass: (info) => joinClassNames(info.inPopover && "fc-breezy-3N5"), + /* Popover + --------------------------------------------------------------------------------------------- */ + popoverClass: "fc-breezy-uhF fc-breezy-wsy fc-breezy-LDJ fc-breezy-hny fc-breezy-pKG fc-breezy-1kP fc-breezy-gMS fc-breezy-aNc fc-breezy-n5m", + popoverCloseClass: `fc-breezy-bCs fc-breezy-1EY fc-breezy-SKv fc-breezy-aYN fc-breezy-KUX fc-breezy-Fvv ${mutedHoverButtonClass} fc-breezy-Z9U`, + popoverCloseContent: () => x(`fc-breezy-XUJ ${mutedFgPressableGroupClass}`), + /* Lane + --------------------------------------------------------------------------------------------- */ + dayLaneClass: (info) => joinClassNames("fc-breezy-wsy", info.isMajor ? "fc-breezy-OFc" : "fc-breezy-tTN", info.isDisabled && "fc-breezy-nYK"), + dayLaneInnerClass: (info) => (info.isStack + ? "fc-breezy-gMS" + : info.isNarrow ? "fc-breezy-148" : "fc-breezy-rVY"), + slotLaneClass: (info) => joinClassNames("fc-breezy-wsy fc-breezy-tTN", info.isMinor && "fc-breezy-TN2"), + /* List Day + --------------------------------------------------------------------------------------------- */ + listDaysClass: "fc-breezy-8T7 fc-breezy-g0K fc-breezy-6u7 fc-breezy-E7b fc-breezy-KRz", + listDayClass: (info) => joinClassNames(!info.isLast && "fc-breezy-zi1 fc-breezy-tTN", "fc-breezy-dl1 fc-breezy-1sP fc-breezy-EF4 fc-breezy-tgZ"), + listDayHeaderClass: "fc-breezy-SEP fc-breezy-yi0 fc-breezy-vVE fc-breezy-kMV fc-breezy-MKw fc-breezy-dl1 fc-breezy-sgX fc-breezy-EF4", + listDayHeaderInnerClass: (info) => joinClassNames("fc-breezy-cJ3 fc-breezy-2rx fc-breezy-Nca fc-breezy-PtF fc-breezy-AAA fc-breezy-9yp", !info.level + ? joinClassNames(info.isToday + ? joinClassNames("fc-breezy-C8a", info.hasNavLink ? primaryPressableClass : primaryClass) + : joinClassNames("fc-breezy-1OT fc-breezy-Oiq", info.hasNavLink && mutedHoverPressableClass)) + : joinClassNames("fc-breezy-mNS", info.hasNavLink && `${mutedHoverPressableClass} fc-breezy-i3P`)), + listDayBodyClass: "fc-breezy-9Qs fc-breezy-1El fc-breezy-2KU fc-breezy-wsy fc-breezy-EAo fc-breezy-KzJ", + /* Single Month (in Multi-Month) + --------------------------------------------------------------------------------------------- */ + singleMonthClass: (info) => joinClassNames(info.multiMonthColumns > 1 && "fc-breezy-jD5", (info.multiMonthColumns === 1 && !info.isLast) && "fc-breezy-zi1 fc-breezy-EAo"), + singleMonthHeaderClass: (info) => joinClassNames(info.multiMonthColumns > 1 + ? "fc-breezy-x96" + : "fc-breezy-End fc-breezy-gmc fc-breezy-zi1 fc-breezy-EAo", "fc-breezy-XpK"), + singleMonthHeaderInnerClass: (info) => joinClassNames("fc-breezy-Jhn fc-breezy-Nca fc-breezy-KzJ fc-breezy-9yp fc-breezy-Oiq fc-breezy-C8a", info.hasNavLink && mutedHoverPressableClass), + /* Misc Table + --------------------------------------------------------------------------------------------- */ + tableHeaderClass: "fc-breezy-gmc", + fillerClass: "fc-breezy-wsy fc-breezy-tTN", + dayNarrowWidth: 100, + dayHeaderRowClass: "fc-breezy-wsy fc-breezy-tTN", + dayRowClass: "fc-breezy-wsy fc-breezy-EAo", + slotHeaderRowClass: "fc-breezy-wsy fc-breezy-EAo", + slotHeaderInnerClass: "fc-breezy-mNS fc-breezy-XHd", + /* Misc Content + --------------------------------------------------------------------------------------------- */ + navLinkClass: `${outlineWidthFocusClass} ${primaryOutlineColorClass}`, + inlineWeekNumberClass: (info) => joinClassNames("fc-breezy-1EY fc-breezy-n9G fc-breezy-iD1 fc-breezy-gmc fc-breezy-t4l fc-breezy-TZ4 fc-breezy-4In fc-breezy-zi1 fc-breezy-k8g fc-breezy-3J4 fc-breezy-Hhp", info.isNarrow + ? `fc-breezy-KUX ${xxsTextClass}` + : "fc-breezy-XJa fc-breezy-a3B", info.hasNavLink + ? `${mutedHoverPressableClass} fc-breezy-07j` + : mutedHoverClass), + highlightClass: "fc-breezy-xAy", + nonBusinessHoursClass: "fc-breezy-nYK", + nowIndicatorLineClass: "fc-breezy-CH7 fc-breezy-qQW fc-breezy-D9l", + nowIndicatorDotClass: "fc-breezy-aAW fc-breezy-Vpk fc-breezy-D9l fc-breezy-63n fc-breezy-AAA fc-breezy-GBJ fc-breezy-9Iz", + /* Resource Day Header + --------------------------------------------------------------------------------------------- */ + resourceDayHeaderAlign: "center", + resourceDayHeaderClass: "fc-breezy-wsy", + resourceDayHeaderInnerClass: (info) => joinClassNames("fc-breezy-bvX fc-breezy-sI1 fc-breezy-C8a", info.isNarrow ? "fc-breezy-a3B" : "fc-breezy-9yp"), + /* Resource Data Grid + --------------------------------------------------------------------------------------------- */ + resourceColumnHeaderClass: "fc-breezy-wsy fc-breezy-tTN fc-breezy-E9P", + resourceColumnHeaderInnerClass: "fc-breezy-bvX fc-breezy-sI1 fc-breezy-9yp", + resourceColumnResizerClass: "fc-breezy-1EY fc-breezy-AWB fc-breezy-4Tv fc-breezy-dnf", + resourceGroupHeaderClass: "fc-breezy-wsy fc-breezy-EAo fc-breezy-pZQ", + resourceGroupHeaderInnerClass: "fc-breezy-bvX fc-breezy-sI1 fc-breezy-9yp", + resourceCellClass: "fc-breezy-wsy fc-breezy-tTN", + resourceCellInnerClass: "fc-breezy-bvX fc-breezy-sI1 fc-breezy-9yp", + resourceIndentClass: "fc-breezy-Wga fc-breezy-p9t fc-breezy-E9P", + resourceExpanderClass: `fc-breezy-bCs fc-breezy-KUX fc-breezy-AAA ${mutedHoverPressableClass} ${outlineWidthFocusClass} ${primaryOutlineColorClass}`, + resourceExpanderContent: (info) => chevronDown(joinClassNames(`fc-breezy-XUJ ${mutedFgPressableGroupClass}`, !info.isExpanded && "fc-breezy-KxI fc-breezy-ZW3")), + resourceHeaderRowClass: "fc-breezy-wsy fc-breezy-EAo", + resourceRowClass: "fc-breezy-wsy fc-breezy-EAo", + resourceColumnDividerClass: "fc-breezy-USt fc-breezy-OFc", + /* Timeline Lane + --------------------------------------------------------------------------------------------- */ + resourceGroupLaneClass: "fc-breezy-wsy fc-breezy-EAo fc-breezy-pZQ", + resourceLaneClass: "fc-breezy-wsy fc-breezy-EAo", + resourceLaneBottomClass: (info) => joinClassNames(info.options.eventOverlap && "fc-breezy-uuA"), + timelineBottomClass: "fc-breezy-uuA", + }, + views: { + dayGrid: { + ...dayRowCommonClasses, + dayHeaderClass: getNormalDayHeaderBorderClass, + dayHeaderDividerClass: "fc-breezy-zi1 fc-breezy-OFc", + dayCellClass: getNormalDayCellBorderColorClass, + dayCellBottomClass: getShortDayCellBottomClass, + backgroundEventInnerClass: "fc-breezy-dl1 fc-breezy-1sP fc-breezy-LMv", + }, + multiMonth: { + ...dayRowCommonClasses, + dayHeaderClass: getNormalDayHeaderBorderClass, + dayHeaderDividerClass: (info) => joinClassNames(info.multiMonthColumns === 1 && "fc-breezy-zi1 fc-breezy-OFc fc-breezy-qNs"), + dayCellClass: getNormalDayCellBorderColorClass, + dayCellBottomClass: getShortDayCellBottomClass, + tableBodyClass: (info) => joinClassNames(info.multiMonthColumns > 1 && "fc-breezy-wsy fc-breezy-EAo fc-breezy-KzJ fc-breezy-eSM fc-breezy-pKG"), + }, + timeGrid: { + ...dayRowCommonClasses, + dayHeaderClass: getMutedDayHeaderBorderClass, + dayHeaderDividerClass: (info) => joinClassNames("fc-breezy-zi1", info.options.allDaySlot + ? "fc-breezy-EAo" + : "fc-breezy-OFc fc-breezy-rf6"), + dayCellClass: getMutedDayCellBorderColorClass, + dayCellBottomClass: tallDayCellBottomClass, + /* TimeGrid > Week Number Header + ------------------------------------------------------------------------------------------- */ + weekNumberHeaderClass: "fc-breezy-XpK fc-breezy-LMv", + weekNumberHeaderInnerClass: (info) => joinClassNames("fc-breezy-cGD fc-breezy-TFV fc-breezy-ZrE fc-breezy-t4l fc-breezy-Fvv fc-breezy-dl1 fc-breezy-1sP fc-breezy-XpK", info.hasNavLink && mutedHoverPressableClass, info.isNarrow ? "fc-breezy-a3B" : "fc-breezy-9yp"), + /* TimeGrid > All-Day Header + ------------------------------------------------------------------------------------------- */ + allDayHeaderClass: "fc-breezy-XpK", + allDayHeaderInnerClass: (info) => joinClassNames("fc-breezy-rUb fc-breezy-mNS", info.isNarrow ? xxsTextClass : "fc-breezy-a3B"), + allDayDividerClass: "fc-breezy-zi1 fc-breezy-OFc fc-breezy-rf6", + /* TimeGrid > Slot Header + ------------------------------------------------------------------------------------------- */ + slotHeaderClass: "fc-breezy-LMv", + slotHeaderInnerClass: (info) => joinClassNames("fc-breezy-eYX fc-breezy-GFf fc-breezy-2tF", info.isNarrow + ? `fc-breezy-Cy2 ${xxsTextClass}` + : "fc-breezy-uqG fc-breezy-a3B", info.isFirst && "fc-breezy-pps"), + slotHeaderDividerClass: "fc-breezy-USt fc-breezy-tTN", + }, + list: { + /* List-View > List-Item Event + ------------------------------------------------------------------------------------------- */ + listItemEventClass: (info) => joinClassNames("fc-breezy-bCs fc-breezy-lqx fc-breezy-XpK fc-breezy-wwb", !info.isLast && "fc-breezy-zi1 fc-breezy-tTN", info.isInteractive + ? faintHoverPressableClass + : faintHoverClass), + listItemEventBeforeClass: "fc-breezy-5JF fc-breezy-lNM fc-breezy-AAA", + listItemEventInnerClass: "fc-breezy-dl1 fc-breezy-1sP fc-breezy-XpK fc-breezy-wwb fc-breezy-9yp", + listItemEventTimeClass: "fc-breezy-yi0 fc-breezy-roZ fc-breezy-kMV fc-breezy-TZ4 fc-breezy-pKG fc-breezy-IPx fc-breezy-t4l", + listItemEventTitleClass: (info) => joinClassNames("fc-breezy-1El fc-breezy-2KU fc-breezy-1OT fc-breezy-TZ4 fc-breezy-pKG fc-breezy-sI1", info.event.url && "fc-breezy-Ogp"), + /* No-Events Screen + ------------------------------------------------------------------------------------------- */ + noEventsClass: "fc-breezy-1El fc-breezy-dl1 fc-breezy-sgX fc-breezy-XpK fc-breezy-E9P", + noEventsInnerClass: "fc-breezy-P9h fc-breezy-t4l", + }, + resourceDayGrid: { + resourceDayHeaderClass: (info) => (info.isMajor + ? "fc-breezy-OFc" + : "fc-breezy-EAo"), + }, + resourceTimeGrid: { + resourceDayHeaderClass: (info) => (info.isMajor + ? "fc-breezy-OFc" + : "fc-breezy-tTN"), + }, + timeline: { + /* Timeline > Row Event + ------------------------------------------------------------------------------------------- */ + rowEventClass: (info) => info.isEnd && "fc-breezy-9hC", + rowEventInnerClass: (info) => info.options.eventOverlap ? "fc-breezy-Jhn" : "fc-breezy-dl6", + /* Timeline > More-Link + ------------------------------------------------------------------------------------------- */ + rowMoreLinkClass: `fc-breezy-9hC fc-breezy-Ika fc-breezy-wsy fc-breezy-d0j fc-breezy-4MR fc-breezy-KzJ ${strongSolidPressableClass} fc-breezy-vwH`, + rowMoreLinkInnerClass: "fc-breezy-iS4 fc-breezy-sI1 fc-breezy-a3B", + /* Timeline > Slot Header + ------------------------------------------------------------------------------------------- */ + slotHeaderAlign: (info) => info.isTime ? "start" : "center", + slotHeaderClass: (info) => joinClassNames(info.level > 0 && "fc-breezy-wsy fc-breezy-tTN", "fc-breezy-LMv"), + slotHeaderInnerClass: (info) => joinClassNames("fc-breezy-GFf fc-breezy-2tF fc-breezy-a3B", info.isTime && joinClassNames("fc-breezy-eYX fc-breezy-OAt", info.isFirst && "fc-breezy-pps"), info.hasNavLink && "fc-breezy-Eu0"), + slotHeaderDividerClass: "fc-breezy-zi1 fc-breezy-OFc fc-breezy-qNs", + }, + }, + }; + /* SVGs + ------------------------------------------------------------------------------------------------- */ + function chevronDown(className) { + return u("svg", { className: className, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", children: u("path", { fillRule: "evenodd", d: "M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z", clipRule: "evenodd" }) }); + } + function chevronDoubleLeft(className) { + return u("svg", { className: className, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", children: u("path", { fillRule: "evenodd", d: "M4.72 9.47a.75.75 0 0 0 0 1.06l4.25 4.25a.75.75 0 1 0 1.06-1.06L6.31 10l3.72-3.72a.75.75 0 1 0-1.06-1.06L4.72 9.47Zm9.25-4.25L9.72 9.47a.75.75 0 0 0 0 1.06l4.25 4.25a.75.75 0 1 0 1.06-1.06L11.31 10l3.72-3.72a.75.75 0 0 0-1.06-1.06Z", clipRule: "evenodd" }) }); + } + function x(className) { + return u("svg", { className: className, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", children: u("path", { d: "M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z" }) }); + } + + globalPlugins.push(index); + +})(FullCalendar.Shared); diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/palettes/rose.css b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/palettes/rose.css new file mode 100644 index 0000000..7fb32bf --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/palettes/rose.css @@ -0,0 +1,90 @@ + +:root { + /* primary */ + --fc-breezy-primary: #e11d48; + --fc-breezy-primary-over: #f43f5e; + --fc-breezy-primary-foreground: #ffffff; + + /* secondary */ + --fc-breezy-secondary: var(--fc-breezy-background); + --fc-breezy-secondary-over: var(--fc-breezy-faint); + --fc-breezy-secondary-border: var(--fc-breezy-strong-border); + --fc-breezy-secondary-foreground: var(--fc-breezy-strong-foreground); + --fc-breezy-secondary-icon: var(--fc-breezy-faint-foreground); + --fc-breezy-secondary-icon-over: var(--fc-breezy-muted-foreground); + + /* calendar content */ + --fc-breezy-event: #ec4899; + --fc-breezy-event-contrast: #fff; + --fc-breezy-background-event: #f97316; + --fc-breezy-highlight: #ef444414; + --fc-breezy-now: #ef4444; + + /* controls */ + --fc-breezy-selected: var(--fc-breezy-strong); + + /* popover */ + --fc-breezy-popover: var(--fc-breezy-background); + --fc-breezy-popover-border: var(--fc-breezy-strong-border); + + /* neutral backgrounds */ + --fc-breezy-background: #ffffff; + --fc-breezy-faint: #00000005; + --fc-breezy-muted: #0000000D; + --fc-breezy-strong: #0000001A; + --fc-breezy-stronger: #00000024; + --fc-breezy-strongest: #0000002E; + + /* neutral foregrounds */ + --fc-breezy-foreground: #374151; + --fc-breezy-faint-foreground: #9ca3af; + --fc-breezy-muted-foreground: #6b7280; + --fc-breezy-strong-foreground: #111827; + + /* neutral borders */ + --fc-breezy-border: #e5e7eb; + --fc-breezy-muted-border: #f3f4f6; + --fc-breezy-strong-border: #d1d5db; +} + +@media not print { + [data-color-scheme=dark] { + /* secondary */ + --fc-breezy-secondary: var(--fc-breezy-muted); + --fc-breezy-secondary-over: var(--fc-breezy-strong); + --fc-breezy-secondary-border: var(--fc-breezy-muted-border); + --fc-breezy-secondary-foreground: var(--fc-breezy-strong-foreground); + --fc-breezy-secondary-icon: var(--fc-breezy-muted-foreground); + --fc-breezy-secondary-icon-over: var(--fc-breezy-foreground); + + /* calendar content */ + --fc-breezy-highlight: #f8717114; + + /* controls */ + --fc-breezy-selected: var(--fc-breezy-muted); + + /* popovers */ + --fc-breezy-popover: #1f2937; + --fc-breezy-popover-border: var(--fc-breezy-border); + + /* neutral backgrounds */ + --fc-breezy-background: #111827; + --fc-breezy-faint: #ffffff08; + --fc-breezy-muted: #ffffff12; + --fc-breezy-strong: #ffffff1F; + --fc-breezy-stronger: #ffffff29; + --fc-breezy-strongest: #ffffff33; + + /* neutral foregrounds */ + --fc-breezy-foreground: #d1d5db; + --fc-breezy-muted-foreground: #9ca3af; + --fc-breezy-faint-foreground: #6b7280; + --fc-breezy-strong-foreground: #ffffff; + + /* neutral borders */ + --fc-breezy-border: #ffffff1A; + --fc-breezy-muted-border: #ffffff0D; + --fc-breezy-strong-border: #ffffff26; + } +} + diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/theme.css b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/theme.css new file mode 100644 index 0000000..493bc97 --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/theme.css @@ -0,0 +1,1093 @@ + +.fc-breezy-n5m { + font-size: 16px; + line-height: 1.5; + --fc-breezy-border-style: solid; +} + +.fc-breezy-Z9U { + appearance: button; + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + opacity: 1; + background-color: #0000; + border-radius: 0; + margin: 0; + border: 0; + padding: 0 +} + +.fc-breezy-JiE { + color: inherit; + text-decoration: inherit; +} + +.fc-breezy-1EY { + position: absolute +} + +.fc-breezy-eYX { + position: relative +} + +.fc-breezy-MaV { + inset-inline: 0 +} + +.fc-breezy-AWB { + inset-block: 0 +} + +.fc-breezy-11a { + inset-inline-start: -4px +} + +.fc-breezy-OAt { + inset-inline-start: -16px +} + +.fc-breezy-Tuc { + inset-inline-end: -4px +} + +.fc-breezy-iD1 { + inset-inline-end: 0 +} + +.fc-breezy-aYN { + inset-inline-end: 8px +} + +.fc-breezy-dnf { + inset-inline-end: -3px +} + +.fc-breezy-YDC { + top: -4px +} + +.fc-breezy-Cy2 { + top: -14px +} + +.fc-breezy-uqG { + top: -16px +} + +.fc-breezy-n9G { + top: 0 +} + +.fc-breezy-ERR { + top: 50% +} + +.fc-breezy-SKv { + top: 8px +} + +.fc-breezy-fJL { + bottom: -4px +} + +.fc-breezy-1V6 { + left: 50% +} + +.fc-breezy-NPw { + order: 1 +} + +.fc-breezy-aAW { + margin: -6px +} + +.fc-breezy-CH7 { + margin: -1px +} + +.fc-breezy-gMS { + margin: 4px +} + +.fc-breezy-cGD { + margin: 6px +} + +.fc-breezy-bvX { + margin: 8px +} + +.fc-breezy-rUb { + margin: 12px +} + +.fc-breezy-jD5 { + margin: 16px +} + +.fc-breezy-PtF { + margin-inline: -8px +} + +.fc-breezy-cKZ { + margin-inline: 2px +} + +.fc-breezy-rVY { + margin-inline: 4px +} + +.fc-breezy-fn8 { + margin-inline: 8px +} + +.fc-breezy-GFf { + margin-inline: 12px +} + +.fc-breezy-g0K { + margin-inline: auto +} + +.fc-breezy-148 { + margin-inline: 1px +} + +.fc-breezy-cJ3 { + margin-block: 2px +} + +.fc-breezy-V9v { + margin-block: 4px +} + +.fc-breezy-2tF { + margin-block: 8px +} + +.fc-breezy-X6C { + margin-block: 12px +} + +.fc-breezy-9Qs { + margin-block: 16px +} + +.fc-breezy-8T7 { + margin-block: 40px +} + +.fc-breezy-SEP { + margin-block: 1px +} + +.fc-breezy-Jzj { + margin-inline-start: 2px +} + +.fc-breezy-Wga { + margin-inline-start: 4px +} + +.fc-breezy-qvL { + margin-inline-start: 1px +} + +.fc-breezy-p9t { + margin-inline-end: -6px +} + +.fc-breezy-3e1 { + margin-inline-end: 2px +} + +.fc-breezy-KYn { + margin-inline-end: 4px +} + +.fc-breezy-9hC { + margin-inline-end: 1px +} + +.fc-breezy-Dq8 { + margin-top: -4px +} + +.fc-breezy-zZM { + margin-top: 4px +} + +.fc-breezy-sEX { + margin-top: 1px +} + +.fc-breezy-vUo { + margin-bottom: 4px +} + +.fc-breezy-Ika { + margin-bottom: 1px +} + +.fc-breezy-F99 { + margin-left: -4px +} + +.fc-breezy-I48 { + display: block +} + +.fc-breezy-dl1 { + display: flex +} + +.fc-breezy-i3N { + display: grid +} + +.fc-breezy-pps { + display: none +} + +.fc-breezy-63n { + width: 0; + height: 0 +} + +.fc-breezy-3wQ { + width: 8px; + height: 8px +} + +.fc-breezy-XUJ { + width: 20px; + height: 20px +} + +.fc-breezy-MSG { + width: 24px; + height: 24px +} + +.fc-breezy-n6w { + width: 32px; + height: 32px +} + +.fc-breezy-uuA { + height: 8px +} + +.fc-breezy-oM6 { + height: 20px +} + +.fc-breezy-TFV { + height: 24px +} + +.fc-breezy-I1A { + height: 32px +} + +.fc-breezy-84e { + min-height: 2px +} + +.fc-breezy-p7s { + min-height: 4px +} + +.fc-breezy-mhE { + min-height: 12px +} + +.fc-breezy-toR { + min-height: 1px +} + +.fc-breezy-roZ { + width: 50% +} + +.fc-breezy-vVE { + width: 25% +} + +.fc-breezy-hza { + width: 8px +} + +.fc-breezy-79F { + width: 20px +} + +.fc-breezy-ilz { + width: 24px +} + +.fc-breezy-4Tv { + width: 5px +} + +.fc-breezy-6u7 { + width: 100% +} + +.fc-breezy-kMV { + max-width: 200px +} + +.fc-breezy-E7b { + max-width: 872px +} + +.fc-breezy-2KU { + min-width: 0 +} + +.fc-breezy-aNc { + min-width: 220px +} + +.fc-breezy-yi0 { + flex-shrink: 0 +} + +.fc-breezy-1Zl { + flex-shrink: 1 +} + +.fc-breezy-OLq { + flex-shrink: 100 +} + +.fc-breezy-1El { + flex-grow: 1 +} + +.fc-breezy-KxI { + rotate: -90deg +} + +.fc-breezy-z44 { + rotate: 90deg +} + +.fc-breezy-jmT { + rotate: 180deg +} + +.fc-breezy-sgX { + flex-direction: column +} + +.fc-breezy-1sP { + flex-direction: row +} + +.fc-breezy-dNl { + flex-wrap: wrap +} + +.fc-breezy-XpK { + align-items: center +} + +.fc-breezy-EF4 { + align-items: flex-start +} + +.fc-breezy-N2M { + justify-content: space-between +} + +.fc-breezy-E9P { + justify-content: center +} + +.fc-breezy-LMv { + justify-content: flex-end +} + +.fc-breezy-NWN { + gap: 4px +} + +.fc-breezy-tgZ { + gap: 8px +} + +.fc-breezy-wwb { + gap: 12px +} + +.fc-breezy-Pms { + gap: 16px +} + +.fc-breezy-sI7 { + align-self: flex-start +} + +.fc-breezy-pKG { + overflow: hidden +} + +.fc-breezy-AAA { + border-radius: 3.40282e38px +} + +.fc-breezy-hny { + border-radius: 8px +} + +.fc-breezy-KzJ { + border-radius: 6px +} + +.fc-breezy-Fvv { + border-radius: 4px +} + +.fc-breezy-QUg { + border-start-start-radius: 6px; + border-end-start-radius: 6px +} + +.fc-breezy-kmj { + border-start-start-radius: 4px; + border-end-start-radius: 4px +} + +.fc-breezy-RNO { + border-start-end-radius: 6px; + border-end-end-radius: 6px +} + +.fc-breezy-Skl { + border-start-end-radius: 4px; + border-end-end-radius: 4px +} + +.fc-breezy-4In { + border-end-start-radius: 6px +} + +.fc-breezy-wko { + border-top-left-radius: 8px; + border-top-right-radius: 8px +} + +.fc-breezy-L2o { + border-bottom-right-radius: 8px; + border-bottom-left-radius: 8px +} + +.fc-breezy-wsy, +.fc-breezy-qQW { + border-style: var(--fc-breezy-border-style); + border-width: 1px +} + +.fc-breezy-5JF { + border-style: var(--fc-breezy-border-style); + border-width: 4px +} + +.fc-breezy-Vpk { + border-style: var(--fc-breezy-border-style); + border-width: 6px +} + +.fc-breezy-1Wx { + border-inline-style: var(--fc-breezy-border-style); + border-inline-width: 1px +} + +.fc-breezy-JIC { + border-block-style: var(--fc-breezy-border-style); + border-block-width: 1px +} + +.fc-breezy-3J4 { + border-inline-start-style: var(--fc-breezy-border-style); + border-inline-start-width: 1px +} + +.fc-breezy-USt { + border-inline-end-style: var(--fc-breezy-border-style); + border-inline-end-width: 1px +} + +.fc-breezy-ku3 { + border-top-style: var(--fc-breezy-border-style); + border-top-width: 1px +} + +.fc-breezy-zi1 { + border-bottom-style: var(--fc-breezy-border-style); + border-bottom-width: 1px +} + +.fc-breezy-TN2 { + --fc-breezy-border-style: dotted; + border-style: dotted +} + +.fc-breezy-EAo { + border-color: var(--fc-breezy-border) +} + +.fc-breezy-tTN { + border-color: var(--fc-breezy-muted-border) +} + +.fc-breezy-D9l { + border-color: var(--fc-breezy-now) +} + +.fc-breezy-LDJ { + border-color: var(--fc-breezy-popover-border) +} + +.fc-breezy-UIT { + border-color: var(--fc-breezy-primary) +} + +.fc-breezy-nuP { + border-color: var(--fc-breezy-secondary-border) +} + +.fc-breezy-OFc { + border-color: var(--fc-breezy-strong-border) +} + +.fc-breezy-lNM { + border-color: var(--fc-event-color) +} + +.fc-breezy-d0j { + border-color: #0000 +} + +.fc-breezy-Hhp { + border-inline-start-color: var(--fc-breezy-border) +} + +.fc-breezy-k8g { + border-bottom-color: var(--fc-breezy-strong-border) +} + +.fc-breezy-gmc { + background-color: var(--fc-breezy-background) +} + +.fc-breezy-nYK { + background-color: var(--fc-breezy-faint) +} + +.fc-breezy-xAy { + background-color: var(--fc-breezy-highlight) +} + +.fc-breezy-pZQ { + background-color: var(--fc-breezy-muted) +} + +.fc-breezy-uhF { + background-color: var(--fc-breezy-popover) +} + +.fc-breezy-Anp { + background-color: var(--fc-breezy-primary) +} + +.fc-breezy-NK3 { + background-color: var(--fc-breezy-secondary) +} + +.fc-breezy-GfU { + background-color: var(--fc-breezy-selected) +} + +@media not print { + .fc-breezy-rf6 { + box-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a + } +} + +@media not print { + @supports (color:color-mix(in lab, red, red)) { + .fc-breezy-aPk { + background-color: color-mix(in oklab, var(--fc-event-color) 8%, transparent) + } + } + @supports not (color:color-mix(in lab, red, red)) { + .fc-breezy-aPk { + position: relative; + isolation: isolate + } + .fc-breezy-aPk:before { + content: ''; + position: absolute; + inset: 0; + background-color: var(--fc-event-color); + opacity: 0.08; + z-index: -1 + } + } +} + +.fc-breezy-GjO { + background-color: var(--fc-event-color) +} + +@supports (color:color-mix(in lab, red, red)) { + .fc-breezy-GjO { + background-color: color-mix(in oklab, var(--fc-event-color)20%, var(--fc-breezy-background)) + } +} + +.fc-breezy-KUX { + padding: 2px +} + +.fc-breezy-iS4 { + padding: 4px +} + +.fc-breezy-XJa { + padding: 6px +} + +.fc-breezy-3N5 { + padding: 8px +} + +.fc-breezy-IJJ { + padding: 12px +} + +.fc-breezy-lqx { + padding: 16px +} + +.fc-breezy-7A6 { + padding: 1px +} + +.fc-breezy-oQ2 { + padding-inline: 2px +} + +.fc-breezy-aCI { + padding-inline: 4px +} + +.fc-breezy-ZrE { + padding-inline: 6px +} + +.fc-breezy-Nca { + padding-inline: 8px +} + +.fc-breezy-Apf { + padding-inline: 12px +} + +.fc-breezy-KRz { + padding-inline: 16px +} + +.fc-breezy-F1o { + padding-inline: 1px +} + +.fc-breezy-2rx { + padding-block: 2px +} + +.fc-breezy-Jhn { + padding-block: 4px +} + +.fc-breezy-End { + padding-block: 6px +} + +.fc-breezy-dl6 { + padding-block: 8px +} + +.fc-breezy-MKw { + padding-block: 14px +} + +.fc-breezy-OEz { + padding-block: 16px +} + +.fc-breezy-P9h { + padding-block: 60px +} + +.fc-breezy-z5u { + padding-block: 1px +} + +.fc-breezy-a7i { + padding-inline-start: 2px +} + +.fc-breezy-C2j { + padding-inline-start: 4px +} + +.fc-breezy-166 { + padding-top: 2px +} + +.fc-breezy-4dx { + padding-top: 4px +} + +.fc-breezy-x96 { + padding-bottom: 4px +} + +.fc-breezy-vQz { + font-size: 11px; + line-height: 1.09091 +} + +.fc-breezy-9ZS { + font-size: 18px; + line-height: calc(1.75/1.125) +} + +.fc-breezy-1Po { + font-size: 16px; + line-height: calc(1.5/1) +} + +.fc-breezy-9yp { + font-size: 14px; + line-height: calc(1.25/.875) +} + +.fc-breezy-a3B { + font-size: 12px; + line-height: calc(1/.75) +} + +.fc-breezy-DIS { + font-weight: 700 +} + +.fc-breezy-1OT { + font-weight: 500 +} + +.fc-breezy-C8a { + font-weight: 600 +} + +.fc-breezy-IPx { + text-overflow: ellipsis +} + +.fc-breezy-TZ4 { + white-space: nowrap +} + +.fc-breezy-jm6 { + white-space: pre +} + +.fc-breezy-mNS { + color: var(--fc-breezy-faint-foreground) +} + +.fc-breezy-sI1 { + color: var(--fc-breezy-foreground) +} + +.fc-breezy-t4l { + color: var(--fc-breezy-muted-foreground) +} + +.fc-breezy-ECg { + color: var(--fc-breezy-primary-foreground) +} + +.fc-breezy-nyY { + color: var(--fc-breezy-secondary-foreground) +} + +.fc-breezy-J7Y { + color: var(--fc-breezy-secondary-icon) +} + +.fc-breezy-Oiq { + color: var(--fc-breezy-strong-foreground) +} + +.fc-breezy-WLU { + color: var(--fc-event-contrast-color) /* hand-picked fallback */ +} + +@supports (color:color-mix(in lab, red, red)) { + .fc-breezy-WLU { + color: color-mix(in oklab, var(--fc-event-color)50%, var(--fc-breezy-foreground)) + } +} + +.fc-breezy-XHd { + text-transform: uppercase +} + +.fc-breezy-L1Y { + font-style: italic +} + +.fc-breezy-lMo { + opacity: .5 +} + +.fc-breezy-iTG { + opacity: .75 +} + +.fc-breezy-1kP { + box-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a +} + +.fc-breezy-tkw { + box-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a +} + +.fc-breezy-qNs { + box-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a +} + +.fc-breezy-eSM { + box-shadow: 0 1px 2px 0 #0000000d +} + +.fc-breezy-A3h { + box-shadow: 0 0 0 1px var(--fc-breezy-ring-color, currentcolor) +} + +.fc-breezy-GBJ { + box-shadow: 0 0 0 2px var(--fc-breezy-ring-color, currentcolor) +} + +.fc-breezy-9Iz { + --fc-breezy-ring-color: var(--fc-breezy-background) +} + +.fc-breezy-2R2 { + outline-style: solid; + outline-width: 1px +} + +.fc-breezy-0Bj { + outline-style: solid; + outline-width: 2px +} + +.fc-breezy-07j { + outline-offset: -1px +} + +.fc-breezy-3Xj { + outline-offset: 2px +} + +.fc-breezy-yg7 { + outline-color: var(--fc-breezy-primary) +} + +.fc-breezy-hS8 { + outline-style: none !important /* HACK */ +} + +.fc-breezy-hJa { + background: linear-gradient(var(--fc-breezy-strong), var(--fc-breezy-strong))var(--fc-breezy-background) +} + +.fc-breezy-iCr:not(:last-child) { + border-bottom-style: var(--fc-breezy-border-style); + border-bottom-width: 1px +} + +@media (hover:hover) { + .fc-breezy-vs6:is(:where(.fc-breezy-bCs):hover *) { + display: block + } + + .fc-breezy-t8l:is(:where(.fc-breezy-bCs):hover *) { + background-color: var(--fc-breezy-primary-over) + } + + .fc-breezy-Wxh:is(:where(.fc-breezy-bCs):hover *) { + color: var(--fc-breezy-foreground) + } + + .fc-breezy-b7A:is(:where(.fc-breezy-bCs):hover *) { + color: var(--fc-breezy-secondary-icon-over) + } + + .fc-breezy-Ogp:is(:where(.fc-breezy-bCs):hover *) { + text-decoration-line: underline + } +} + +.fc-breezy-x7E:is(:where(.fc-breezy-bCs):focus-visible *) { + color: var(--fc-breezy-foreground) +} + +.fc-breezy-tRE:is(:where(.fc-breezy-bCs):focus-visible *) { + color: var(--fc-breezy-secondary-icon-over) +} + +.fc-breezy-nPR:is(:where(.fc-breezy-bCs):focus-visible *) { + outline-style: solid; + outline-width: 2px +} + +.fc-breezy-Ps8:first-child { + border-start-start-radius: 6px; + border-end-start-radius: 6px +} + +.fc-breezy-H1W:first-child { + border-inline-start-style: var(--fc-breezy-border-style); + border-inline-start-width: 1px +} + +.fc-breezy-g3A:last-child { + border-start-end-radius: 6px; + border-end-end-radius: 6px +} + +.fc-breezy-7ss:last-child { + border-inline-end-style: var(--fc-breezy-border-style); + border-inline-end-width: 1px +} + +@media (hover:hover) { + .fc-breezy-1EL:hover { + background-color: var(--fc-breezy-faint) + } + + .fc-breezy-qiG:hover { + background-color: var(--fc-breezy-muted) + } + + .fc-breezy-8mf:hover { + background-color: var(--fc-breezy-primary-over) + } + + .fc-breezy-StK:hover { + background-color: var(--fc-breezy-secondary-over) + } + + .fc-breezy-yE8:hover { + background-color: var(--fc-event-color) + } + + @supports (color:color-mix(in lab, red, red)) { + .fc-breezy-yE8:hover { + background-color: color-mix(in oklab, var(--fc-event-color)25%, var(--fc-breezy-background)) + } + } + + .fc-breezy-i3P:hover { + color: var(--fc-breezy-muted-foreground) + } + + .fc-breezy-hnE:hover { + color: var(--fc-breezy-strong-foreground) + } + + .fc-breezy-Eu0:hover { + text-decoration-line: underline + } + + .fc-breezy-AT7:hover { + background: linear-gradient(var(--fc-breezy-stronger), var(--fc-breezy-stronger))var(--fc-breezy-background) + } +} + +.fc-breezy-UTk:focus-visible { + background-color: var(--fc-breezy-faint) +} + +.fc-breezy-WfX:focus-visible { + background-color: var(--fc-breezy-muted) +} + +.fc-breezy-uqo:focus-visible { + outline-style: solid; + outline-width: 2px +} + +.fc-breezy-qWF:active { + background-color: var(--fc-breezy-muted) +} + +.fc-breezy-x2T:active { + background-color: var(--fc-event-color) +} + +@supports (color:color-mix(in lab, red, red)) { + .fc-breezy-x2T:active { + background-color: color-mix(in oklab, var(--fc-event-color)30%, var(--fc-breezy-background)) + } +} + +.fc-breezy-b3B:active { + background: linear-gradient(var(--fc-breezy-strongest), var(--fc-breezy-strongest))var(--fc-breezy-background) +} + +@media print { + .fc-breezy-jsy { + border-width: 1px + } + + .fc-breezy-DO7 { + border-color: var(--fc-event-color) + } + + .fc-breezy-4MR { + border-color: #000 + } + + .fc-breezy-vwH { + background-color: #fff + } +} + +[dir=rtl] .fc-breezy-keW { + rotate: -90deg +} + +[dir=rtl] .fc-breezy-jY6 { + rotate: none +} + +[dir=rtl] .fc-breezy-ZW3 { + rotate: 90deg +} + +[dir=rtl] .fc-breezy-asP { + rotate: 180deg +} + diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/ical.es5.cjs b/hosts/vm/mediakiwi/FullCalendar/resources/ical.es5.cjs new file mode 100644 index 0000000..9179303 --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/resources/ical.es5.cjs @@ -0,0 +1,9717 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ICAL = factory()); +})(this, (function () { 'use strict'; + + function _arrayLikeToArray(r, a) { + (null == a || a > r.length) && (a = r.length); + for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; + return n; + } + function _arrayWithHoles(r) { + if (Array.isArray(r)) return r; + } + function _arrayWithoutHoles(r) { + if (Array.isArray(r)) return _arrayLikeToArray(r); + } + function _assertClassBrand(e, t, n) { + if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; + throw new TypeError("Private element is not present on this object"); + } + function _assertThisInitialized(e) { + if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + return e; + } + function _callSuper(t, o, e) { + return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); + } + function _classCallCheck(a, n) { + if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); + } + function _construct(t, e, r) { + if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments); + var o = [null]; + o.push.apply(o, e); + var p = new (t.bind.apply(t, o))(); + return r && _setPrototypeOf(p, r.prototype), p; + } + function _defineProperties(e, r) { + for (var t = 0; t < r.length; t++) { + var o = r[t]; + o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); + } + } + function _createClass(e, r, t) { + return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { + writable: !1 + }), e; + } + function _createForOfIteratorHelper(r, e) { + var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; + if (!t) { + if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) { + t && (r = t); + var n = 0, + F = function () {}; + return { + s: F, + n: function () { + return n >= r.length ? { + done: !0 + } : { + done: !1, + value: r[n++] + }; + }, + e: function (r) { + throw r; + }, + f: F + }; + } + throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + var o, + a = !0, + u = !1; + return { + s: function () { + t = t.call(r); + }, + n: function () { + var r = t.next(); + return a = r.done, r; + }, + e: function (r) { + u = !0, o = r; + }, + f: function () { + try { + a || null == t.return || t.return(); + } finally { + if (u) throw o; + } + } + }; + } + function _defineProperty(e, r, t) { + return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { + value: t, + enumerable: !0, + configurable: !0, + writable: !0 + }) : e[r] = t, e; + } + function _getPrototypeOf(t) { + return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { + return t.__proto__ || Object.getPrototypeOf(t); + }, _getPrototypeOf(t); + } + function _inherits(t, e) { + if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); + t.prototype = Object.create(e && e.prototype, { + constructor: { + value: t, + writable: !0, + configurable: !0 + } + }), Object.defineProperty(t, "prototype", { + writable: !1 + }), e && _setPrototypeOf(t, e); + } + function _isNativeFunction(t) { + try { + return -1 !== Function.toString.call(t).indexOf("[native code]"); + } catch (n) { + return "function" == typeof t; + } + } + function _isNativeReflectConstruct() { + try { + var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); + } catch (t) {} + return (_isNativeReflectConstruct = function () { + return !!t; + })(); + } + function _iterableToArray(r) { + if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); + } + function _iterableToArrayLimit(r, l) { + var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; + if (null != t) { + var e, + n, + i, + u, + a = [], + f = !0, + o = !1; + try { + if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); + } catch (r) { + o = !0, n = r; + } finally { + try { + if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; + } finally { + if (o) throw n; + } + } + return a; + } + } + function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + function _nonIterableSpread() { + throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + function _possibleConstructorReturn(t, e) { + if (e && ("object" == typeof e || "function" == typeof e)) return e; + if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); + return _assertThisInitialized(t); + } + function _setPrototypeOf(t, e) { + return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { + return t.__proto__ = e, t; + }, _setPrototypeOf(t, e); + } + function _slicedToArray(r, e) { + return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); + } + function _toConsumableArray(r) { + return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); + } + function _toPrimitive(t, r) { + if ("object" != typeof t || !t) return t; + var e = t[Symbol.toPrimitive]; + if (void 0 !== e) { + var i = e.call(t, r || "default"); + if ("object" != typeof i) return i; + throw new TypeError("@@toPrimitive must return a primitive value."); + } + return ("string" === r ? String : Number)(t); + } + function _toPropertyKey(t) { + var i = _toPrimitive(t, "string"); + return "symbol" == typeof i ? i : i + ""; + } + function _typeof(o) { + "@babel/helpers - typeof"; + + return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { + return typeof o; + } : function (o) { + return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; + }, _typeof(o); + } + function _unsupportedIterableToArray(r, a) { + if (r) { + if ("string" == typeof r) return _arrayLikeToArray(r, a); + var t = {}.toString.call(r).slice(8, -1); + return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; + } + } + function _wrapNativeSuper(t) { + var r = "function" == typeof Map ? new Map() : void 0; + return _wrapNativeSuper = function (t) { + if (null === t || !_isNativeFunction(t)) return t; + if ("function" != typeof t) throw new TypeError("Super expression must either be null or a function"); + if (void 0 !== r) { + if (r.has(t)) return r.get(t); + r.set(t, Wrapper); + } + function Wrapper() { + return _construct(t, arguments, _getPrototypeOf(this).constructor); + } + return Wrapper.prototype = Object.create(t.prototype, { + constructor: { + value: Wrapper, + enumerable: !1, + writable: !0, + configurable: !0 + } + }), _setPrototypeOf(Wrapper, t); + }, _wrapNativeSuper(t); + } + + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + /** + * Represents the BINARY value type, which contains extra methods for encoding and decoding. + * + * @memberof ICAL + */ + var Binary = /*#__PURE__*/function () { + /** + * Creates a new ICAL.Binary instance + * + * @param {String} aValue The binary data for this value + */ + function Binary(aValue) { + _classCallCheck(this, Binary); + /** + * The type name, to be used in the jCal object. + * @default "binary" + * @constant + */ + _defineProperty(this, "icaltype", "binary"); + this.value = aValue; + } + return _createClass(Binary, [{ + key: "decodeValue", + value: + /** + * Base64 decode the current value + * + * @return {String} The base64-decoded value + */ + function decodeValue() { + return this._b64_decode(this.value); + } + + /** + * Encodes the passed parameter with base64 and sets the internal + * value to the result. + * + * @param {String} aValue The raw binary value to encode + */ + }, { + key: "setEncodedValue", + value: function setEncodedValue(aValue) { + this.value = this._b64_encode(aValue); + } + }, { + key: "_b64_encode", + value: function _b64_encode(data) { + // http://kevin.vanzonneveld.net + // + original by: Tyler Akins (http://rumkin.com) + // + improved by: Bayron Guevara + // + improved by: Thunder.m + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Pellentesque Malesuada + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: Rafał Kukawski (http://kukawski.pl) + // * example 1: base64_encode('Kevin van Zonneveld'); + // * returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA==' + // mozilla has this native + // - but breaks in 2.0.0.12! + //if (typeof this.window['atob'] == 'function') { + // return atob(data); + //} + var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789+/="; + var o1, + o2, + o3, + h1, + h2, + h3, + h4, + bits, + i = 0, + ac = 0, + enc = "", + tmp_arr = []; + if (!data) { + return data; + } + do { + // pack three octets into four hexets + o1 = data.charCodeAt(i++); + o2 = data.charCodeAt(i++); + o3 = data.charCodeAt(i++); + bits = o1 << 16 | o2 << 8 | o3; + h1 = bits >> 18 & 0x3f; + h2 = bits >> 12 & 0x3f; + h3 = bits >> 6 & 0x3f; + h4 = bits & 0x3f; + + // use hexets to index into b64, and append result to encoded string + tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); + } while (i < data.length); + enc = tmp_arr.join(''); + var r = data.length % 3; + return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3); + } + }, { + key: "_b64_decode", + value: function _b64_decode(data) { + // http://kevin.vanzonneveld.net + // + original by: Tyler Akins (http://rumkin.com) + // + improved by: Thunder.m + // + input by: Aman Gupta + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Onno Marsman + // + bugfixed by: Pellentesque Malesuada + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + input by: Brett Zamir (http://brett-zamir.me) + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // * example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA=='); + // * returns 1: 'Kevin van Zonneveld' + // mozilla has this native + // - but breaks in 2.0.0.12! + //if (typeof this.window['btoa'] == 'function') { + // return btoa(data); + //} + var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789+/="; + var o1, + o2, + o3, + h1, + h2, + h3, + h4, + bits, + i = 0, + ac = 0, + dec = "", + tmp_arr = []; + if (!data) { + return data; + } + data += ''; + do { + // unpack four hexets into three octets using index points in b64 + h1 = b64.indexOf(data.charAt(i++)); + h2 = b64.indexOf(data.charAt(i++)); + h3 = b64.indexOf(data.charAt(i++)); + h4 = b64.indexOf(data.charAt(i++)); + bits = h1 << 18 | h2 << 12 | h3 << 6 | h4; + o1 = bits >> 16 & 0xff; + o2 = bits >> 8 & 0xff; + o3 = bits & 0xff; + if (h3 == 64) { + tmp_arr[ac++] = String.fromCharCode(o1); + } else if (h4 == 64) { + tmp_arr[ac++] = String.fromCharCode(o1, o2); + } else { + tmp_arr[ac++] = String.fromCharCode(o1, o2, o3); + } + } while (i < data.length); + dec = tmp_arr.join(''); + return dec; + } + + /** + * The string representation of this value + * @return {String} + */ + }, { + key: "toString", + value: function toString() { + return this.value; + } + }], [{ + key: "fromString", + value: + /** + * Creates a binary value from the given string. + * + * @param {String} aString The binary value string + * @return {Binary} The binary value instance + */ + function fromString(aString) { + return new Binary(aString); + } + }]); + }(); + + var DURATION_LETTERS = /([PDWHMTS]{1,1})/; + var DATA_PROPS_TO_COPY = ["weeks", "days", "hours", "minutes", "seconds", "isNegative"]; + + /** + * This class represents the "duration" value type, with various calculation + * and manipulation methods. + * + * @memberof ICAL + */ + var Duration = /*#__PURE__*/function () { + /** + * Creates a new ICAL.Duration instance. + * + * @param {Object} data An object with members of the duration + * @param {Number=} data.weeks Duration in weeks + * @param {Number=} data.days Duration in days + * @param {Number=} data.hours Duration in hours + * @param {Number=} data.minutes Duration in minutes + * @param {Number=} data.seconds Duration in seconds + * @param {Boolean=} data.isNegative If true, the duration is negative + */ + function Duration(data) { + _classCallCheck(this, Duration); + /** + * The weeks in this duration + * @type {Number} + * @default 0 + */ + _defineProperty(this, "weeks", 0); + /** + * The days in this duration + * @type {Number} + * @default 0 + */ + _defineProperty(this, "days", 0); + /** + * The days in this duration + * @type {Number} + * @default 0 + */ + _defineProperty(this, "hours", 0); + /** + * The minutes in this duration + * @type {Number} + * @default 0 + */ + _defineProperty(this, "minutes", 0); + /** + * The seconds in this duration + * @type {Number} + * @default 0 + */ + _defineProperty(this, "seconds", 0); + /** + * The seconds in this duration + * @type {Boolean} + * @default false + */ + _defineProperty(this, "isNegative", false); + /** + * The class identifier. + * @constant + * @type {String} + * @default "icalduration" + */ + _defineProperty(this, "icalclass", "icalduration"); + /** + * The type name, to be used in the jCal object. + * @constant + * @type {String} + * @default "duration" + */ + _defineProperty(this, "icaltype", "duration"); + this.wrappedJSObject = this; + this.fromData(data); + } + return _createClass(Duration, [{ + key: "clone", + value: + /** + * Returns a clone of the duration object. + * + * @return {Duration} The cloned object + */ + function clone() { + return Duration.fromData(this); + } + + /** + * The duration value expressed as a number of seconds. + * + * @return {Number} The duration value in seconds + */ + }, { + key: "toSeconds", + value: function toSeconds() { + var seconds = this.seconds + 60 * this.minutes + 3600 * this.hours + 86400 * this.days + 7 * 86400 * this.weeks; + return this.isNegative ? -seconds : seconds; + } + + /** + * Reads the passed seconds value into this duration object. Afterwards, + * members like {@link ICAL.Duration#days days} and {@link ICAL.Duration#weeks weeks} will be set up + * accordingly. + * + * @param {Number} aSeconds The duration value in seconds + * @return {Duration} Returns this instance + */ + }, { + key: "fromSeconds", + value: function fromSeconds(aSeconds) { + var secs = Math.abs(aSeconds); + this.isNegative = aSeconds < 0; + this.days = trunc(secs / 86400); + + // If we have a flat number of weeks, use them. + if (this.days % 7 == 0) { + this.weeks = this.days / 7; + this.days = 0; + } else { + this.weeks = 0; + } + secs -= (this.days + 7 * this.weeks) * 86400; + this.hours = trunc(secs / 3600); + secs -= this.hours * 3600; + this.minutes = trunc(secs / 60); + secs -= this.minutes * 60; + this.seconds = secs; + return this; + } + + /** + * Sets up the current instance using members from the passed data object. + * + * @param {Object} aData An object with members of the duration + * @param {Number=} aData.weeks Duration in weeks + * @param {Number=} aData.days Duration in days + * @param {Number=} aData.hours Duration in hours + * @param {Number=} aData.minutes Duration in minutes + * @param {Number=} aData.seconds Duration in seconds + * @param {Boolean=} aData.isNegative If true, the duration is negative + */ + }, { + key: "fromData", + value: function fromData(aData) { + for (var _i = 0, _DATA_PROPS_TO_COPY = DATA_PROPS_TO_COPY; _i < _DATA_PROPS_TO_COPY.length; _i++) { + var prop = _DATA_PROPS_TO_COPY[_i]; + if (aData && prop in aData) { + this[prop] = aData[prop]; + } else { + this[prop] = 0; + } + } + } + + /** + * Resets the duration instance to the default values, i.e. PT0S + */ + }, { + key: "reset", + value: function reset() { + this.isNegative = false; + this.weeks = 0; + this.days = 0; + this.hours = 0; + this.minutes = 0; + this.seconds = 0; + } + + /** + * Compares the duration instance with another one. + * + * @param {Duration} aOther The instance to compare with + * @return {Number} -1, 0 or 1 for less/equal/greater + */ + }, { + key: "compare", + value: function compare(aOther) { + var thisSeconds = this.toSeconds(); + var otherSeconds = aOther.toSeconds(); + return (thisSeconds > otherSeconds) - (thisSeconds < otherSeconds); + } + + /** + * Normalizes the duration instance. For example, a duration with a value + * of 61 seconds will be normalized to 1 minute and 1 second. + */ + }, { + key: "normalize", + value: function normalize() { + this.fromSeconds(this.toSeconds()); + } + + /** + * The string representation of this duration. + * @return {String} + */ + }, { + key: "toString", + value: function toString() { + if (this.toSeconds() == 0) { + return "PT0S"; + } else { + var str = ""; + if (this.isNegative) str += "-"; + str += "P"; + var hasWeeks = false; + if (this.weeks) { + if (this.days || this.hours || this.minutes || this.seconds) { + str += this.weeks * 7 + this.days + "D"; + } else { + str += this.weeks + "W"; + hasWeeks = true; + } + } else if (this.days) { + str += this.days + "D"; + } + if (!hasWeeks) { + if (this.hours || this.minutes || this.seconds) { + str += "T"; + if (this.hours) { + str += this.hours + "H"; + } + if (this.minutes) { + str += this.minutes + "M"; + } + if (this.seconds) { + str += this.seconds + "S"; + } + } + } + return str; + } + } + + /** + * The iCalendar string representation of this duration. + * @return {String} + */ + }, { + key: "toICALString", + value: function toICALString() { + return this.toString(); + } + }], [{ + key: "fromSeconds", + value: + /** + * Returns a new ICAL.Duration instance from the passed seconds value. + * + * @param {Number} aSeconds The seconds to create the instance from + * @return {Duration} The newly created duration instance + */ + function fromSeconds(aSeconds) { + return new Duration().fromSeconds(aSeconds); + } + + /** + * Checks if the given string is an iCalendar duration value. + * + * @param {String} value The raw ical value + * @return {Boolean} True, if the given value is of the + * duration ical type + */ + }, { + key: "isValueString", + value: function isValueString(string) { + return string[0] === 'P' || string[1] === 'P'; + } + + /** + * Creates a new {@link ICAL.Duration} instance from the passed string. + * + * @param {String} aStr The string to parse + * @return {Duration} The created duration instance + */ + }, { + key: "fromString", + value: function fromString(aStr) { + var pos = 0; + var dict = Object.create(null); + var chunks = 0; + while ((pos = aStr.search(DURATION_LETTERS)) !== -1) { + var type = aStr[pos]; + var numeric = aStr.slice(0, Math.max(0, pos)); + aStr = aStr.slice(pos + 1); + chunks += parseDurationChunk(type, numeric, dict); + } + if (chunks < 2) { + // There must be at least a chunk with "P" and some unit chunk + throw new Error('invalid duration value: Not enough duration components in "' + aStr + '"'); + } + return new Duration(dict); + } + + /** + * Creates a new ICAL.Duration instance from the given data object. + * + * @param {Object} aData An object with members of the duration + * @param {Number=} aData.weeks Duration in weeks + * @param {Number=} aData.days Duration in days + * @param {Number=} aData.hours Duration in hours + * @param {Number=} aData.minutes Duration in minutes + * @param {Number=} aData.seconds Duration in seconds + * @param {Boolean=} aData.isNegative If true, the duration is negative + * @return {Duration} The createad duration instance + */ + }, { + key: "fromData", + value: function fromData(aData) { + return new Duration(aData); + } + }]); + }(); + + /** + * Internal helper function to handle a chunk of a duration. + * + * @private + * @param {String} letter type of duration chunk + * @param {String} number numeric value or -/+ + * @param {Object} dict target to assign values to + */ + function parseDurationChunk(letter, number, object) { + var type; + switch (letter) { + case 'P': + if (number && number === '-') { + object.isNegative = true; + } else { + object.isNegative = false; + } + // period + break; + case 'D': + type = 'days'; + break; + case 'W': + type = 'weeks'; + break; + case 'H': + type = 'hours'; + break; + case 'M': + type = 'minutes'; + break; + case 'S': + type = 'seconds'; + break; + default: + // Not a valid chunk + return 0; + } + if (type) { + if (!number && number !== 0) { + throw new Error('invalid duration value: Missing number before "' + letter + '"'); + } + var num = parseInt(number, 10); + if (isStrictlyNaN(num)) { + throw new Error('invalid duration value: Invalid number "' + number + '" before "' + letter + '"'); + } + object[type] = num; + } + return 1; + } + + /** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * @ignore + * @typedef {import("./types.js").jCalComponent} jCalComponent + * Imports the 'occurrenceDetails' type from the "types.js" module + */ + + /** + * This class represents the "period" value type, with various calculation and manipulation methods. + * + * @memberof ICAL + */ + var Period = /*#__PURE__*/function () { + /** + * Creates a new ICAL.Period instance. The passed data object cannot contain both and end date and + * a duration. + * + * @param {Object} aData An object with members of the period + * @param {Time=} aData.start The start of the period + * @param {Time=} aData.end The end of the period + * @param {Duration=} aData.duration The duration of the period + */ + function Period(aData) { + _classCallCheck(this, Period); + /** + * The start of the period + * @type {Time} + */ + _defineProperty(this, "start", null); + /** + * The end of the period + * @type {Time} + */ + _defineProperty(this, "end", null); + /** + * The duration of the period + * @type {Duration} + */ + _defineProperty(this, "duration", null); + /** + * The class identifier. + * @constant + * @type {String} + * @default "icalperiod" + */ + _defineProperty(this, "icalclass", "icalperiod"); + /** + * The type name, to be used in the jCal object. + * @constant + * @type {String} + * @default "period" + */ + _defineProperty(this, "icaltype", "period"); + this.wrappedJSObject = this; + if (aData && 'start' in aData) { + if (aData.start && !(aData.start instanceof Time)) { + throw new TypeError('.start must be an instance of ICAL.Time'); + } + this.start = aData.start; + } + if (aData && aData.end && aData.duration) { + throw new Error('cannot accept both end and duration'); + } + if (aData && 'end' in aData) { + if (aData.end && !(aData.end instanceof Time)) { + throw new TypeError('.end must be an instance of ICAL.Time'); + } + this.end = aData.end; + } + if (aData && 'duration' in aData) { + if (aData.duration && !(aData.duration instanceof Duration)) { + throw new TypeError('.duration must be an instance of ICAL.Duration'); + } + this.duration = aData.duration; + } + } + return _createClass(Period, [{ + key: "clone", + value: + /** + * Returns a clone of the duration object. + * + * @return {Period} The cloned object + */ + function clone() { + return Period.fromData({ + start: this.start ? this.start.clone() : null, + end: this.end ? this.end.clone() : null, + duration: this.duration ? this.duration.clone() : null + }); + } + + /** + * Calculates the duration of the period, either directly or by subtracting + * start from end date. + * + * @return {Duration} The calculated duration + */ + }, { + key: "getDuration", + value: function getDuration() { + if (this.duration) { + return this.duration; + } else { + return this.end.subtractDate(this.start); + } + } + + /** + * Calculates the end date of the period, either directly or by adding + * duration to start date. + * + * @return {Time} The calculated end date + */ + }, { + key: "getEnd", + value: function getEnd() { + if (this.end) { + return this.end; + } else { + var end = this.start.clone(); + end.addDuration(this.duration); + return end; + } + } + + /** + * Compare this period with a date or other period. To maintain the logic where a.compare(b) + * returns 1 when a > b, this function will return 1 when the period is after the date, 0 when the + * date is within the period, and -1 when the period is before the date. When comparing two + * periods, as soon as they overlap in any way this will return 0. + * + * @param {Time|Period} dt The date or other period to compare with + */ + }, { + key: "compare", + value: function compare(dt) { + if (dt.compare(this.start) < 0) { + return 1; + } else if (dt.compare(this.getEnd()) > 0) { + return -1; + } else { + return 0; + } + } + + /** + * The string representation of this period. + * @return {String} + */ + }, { + key: "toString", + value: function toString() { + return this.start + "/" + (this.end || this.duration); + } + + /** + * The jCal representation of this period type. + * @return {Object} + */ + }, { + key: "toJSON", + value: function toJSON() { + return [this.start.toString(), (this.end || this.duration).toString()]; + } + + /** + * The iCalendar string representation of this period. + * @return {String} + */ + }, { + key: "toICALString", + value: function toICALString() { + return this.start.toICALString() + "/" + (this.end || this.duration).toICALString(); + } + }], [{ + key: "fromString", + value: + /** + * Creates a new {@link ICAL.Period} instance from the passed string. + * + * @param {String} str The string to parse + * @param {Property} prop The property this period will be on + * @return {Period} The created period instance + */ + function fromString(str, prop) { + var parts = str.split('/'); + if (parts.length !== 2) { + throw new Error('Invalid string value: "' + str + '" must contain a "/" char.'); + } + var options = { + start: Time.fromDateTimeString(parts[0], prop) + }; + var end = parts[1]; + if (Duration.isValueString(end)) { + options.duration = Duration.fromString(end); + } else { + options.end = Time.fromDateTimeString(end, prop); + } + return new Period(options); + } + + /** + * Creates a new {@link ICAL.Period} instance from the given data object. + * The passed data object cannot contain both and end date and a duration. + * + * @param {Object} aData An object with members of the period + * @param {Time=} aData.start The start of the period + * @param {Time=} aData.end The end of the period + * @param {Duration=} aData.duration The duration of the period + * @return {Period} The period instance + */ + }, { + key: "fromData", + value: function fromData(aData) { + return new Period(aData); + } + + /** + * Returns a new period instance from the given jCal data array. The first + * member is always the start date string, the second member is either a + * duration or end date string. + * + * @param {jCalComponent} aData The jCal data array + * @param {Property} aProp The property this jCal data is on + * @param {Boolean} aLenient If true, data value can be both date and date-time + * @return {Period} The period instance + */ + }, { + key: "fromJSON", + value: function fromJSON(aData, aProp, aLenient) { + function fromDateOrDateTimeString(aValue, dateProp) { + if (aLenient) { + return Time.fromString(aValue, dateProp); + } else { + return Time.fromDateTimeString(aValue, dateProp); + } + } + if (Duration.isValueString(aData[1])) { + return Period.fromData({ + start: fromDateOrDateTimeString(aData[0], aProp), + duration: Duration.fromString(aData[1]) + }); + } else { + return Period.fromData({ + start: fromDateOrDateTimeString(aData[0], aProp), + end: fromDateOrDateTimeString(aData[1], aProp) + }); + } + } + }]); + }(); + + var _Time; + + /** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * + * @ignore + * @typedef {import("./types.js").weekDay} weekDay + * Imports the 'weekDay' type from the "types.js" module + * + * @ignore + * @typedef {import("./types.js").timeInit} timeInit + * Imports the 'timeInit' type from the "types.js" module + */ + + /** + * @classdesc + * iCalendar Time representation (similar to JS Date object). Fully + * independent of system (OS) timezone / time. Unlike JS Date, the month + * January is 1, not zero. + * + * @example + * var time = new ICAL.Time({ + * year: 2012, + * month: 10, + * day: 11 + * minute: 0, + * second: 0, + * isDate: false + * }); + * + * + * @memberof ICAL + */ + var Time = /*#__PURE__*/function () { + // MONDAY + + /** + * Creates a new ICAL.Time instance. + * + * @param {timeInit} data Time initialization + * @param {Timezone} zone timezone this position occurs in + */ + function Time(data, zone) { + _classCallCheck(this, Time); + /** + * The class identifier. + * @constant + * @type {String} + * @default "icaltime" + */ + _defineProperty(this, "icalclass", "icaltime"); + _defineProperty(this, "_cachedUnixTime", null); + /** + * The timezone for this time. + * @type {Timezone} + */ + _defineProperty(this, "zone", null); + /** + * Internal uses to indicate that a change has been made and the next read + * operation must attempt to normalize the value (for example changing the + * day to 33). + * + * @type {Boolean} + * @private + */ + _defineProperty(this, "_pendingNormalization", false); + this.wrappedJSObject = this; + + /** + * @type {timeInit} + * @private + */ + this._time = Object.create(null); + + /* time defaults */ + this._time.year = 0; + this._time.month = 1; + this._time.day = 1; + this._time.hour = 0; + this._time.minute = 0; + this._time.second = 0; + this._time.isDate = false; + this.fromData(data, zone); + } + return _createClass(Time, [{ + key: "icaltype", + get: + /** + * The type name, to be used in the jCal object. This value may change and + * is strictly defined by the {@link ICAL.Time#isDate isDate} member. + * @type {String} + * @default "date-time" + */ + function get() { + return this.isDate ? 'date' : 'date-time'; + } + }, { + key: "year", + get: + /** + * The year of this date. + * @type {Number} + */ + function get() { + return this._getTimeAttr('year'); + }, + set: function set(val) { + this._setTimeAttr('year', val); + } + + /** + * The month of this date. + * @type {Number} + */ + }, { + key: "month", + get: function get() { + return this._getTimeAttr('month'); + }, + set: function set(val) { + this._setTimeAttr('month', val); + } + + /** + * The day of this date. + * @type {Number} + */ + }, { + key: "day", + get: function get() { + return this._getTimeAttr('day'); + }, + set: function set(val) { + this._setTimeAttr('day', val); + } + + /** + * The hour of this date-time. + * @type {Number} + */ + }, { + key: "hour", + get: function get() { + return this._getTimeAttr('hour'); + }, + set: function set(val) { + this._setTimeAttr('hour', val); + } + + /** + * The minute of this date-time. + * @type {Number} + */ + }, { + key: "minute", + get: function get() { + return this._getTimeAttr('minute'); + }, + set: function set(val) { + this._setTimeAttr('minute', val); + } + + /** + * The second of this date-time. + * @type {Number} + */ + }, { + key: "second", + get: function get() { + return this._getTimeAttr('second'); + }, + set: function set(val) { + this._setTimeAttr('second', val); + } + + /** + * If true, the instance represents a date (as opposed to a date-time) + * @type {Boolean} + */ + }, { + key: "isDate", + get: function get() { + return this._getTimeAttr('isDate'); + }, + set: function set(val) { + this._setTimeAttr('isDate', val); + } + + /** + * @private + * @param {String} attr Attribute to get (one of: year, month, + * day, hour, minute, second, isDate) + * @return {Number|Boolean} Current value for the attribute + */ + }, { + key: "_getTimeAttr", + value: function _getTimeAttr(attr) { + if (this._pendingNormalization) { + this._normalize(); + this._pendingNormalization = false; + } + return this._time[attr]; + } + + /** + * @private + * @param {String} attr Attribute to set (one of: year, month, + * day, hour, minute, second, isDate) + * @param {Number|Boolean} val New value for the attribute + */ + }, { + key: "_setTimeAttr", + value: function _setTimeAttr(attr, val) { + // Check if isDate will be set and if was not set to normalize date. + // This avoids losing days when seconds, minutes and hours are zeroed + // what normalize will do when time is a date. + if (attr === "isDate" && val && !this._time.isDate) { + this.adjust(0, 0, 0, 0); + } + this._cachedUnixTime = null; + this._pendingNormalization = true; + this._time[attr] = val; + } + + /** + * Returns a clone of the time object. + * + * @return {Time} The cloned object + */ + }, { + key: "clone", + value: function clone() { + return new Time(this._time, this.zone); + } + + /** + * Reset the time instance to epoch time + */ + }, { + key: "reset", + value: function reset() { + this.fromData(Time.epochTime); + this.zone = Timezone.utcTimezone; + } + + /** + * Reset the time instance to the given date/time values. + * + * @param {Number} year The year to set + * @param {Number} month The month to set + * @param {Number} day The day to set + * @param {Number} hour The hour to set + * @param {Number} minute The minute to set + * @param {Number} second The second to set + * @param {Timezone} timezone The timezone to set + */ + }, { + key: "resetTo", + value: function resetTo(year, month, day, hour, minute, second, timezone) { + this.fromData({ + year: year, + month: month, + day: day, + hour: hour, + minute: minute, + second: second, + zone: timezone + }); + } + + /** + * Set up the current instance from the Javascript date value. + * + * @param {?Date} aDate The Javascript Date to read, or null to reset + * @param {Boolean} [useUTC=false] If true, the UTC values of the date will be used + */ + }, { + key: "fromJSDate", + value: function fromJSDate(aDate, useUTC) { + if (!aDate) { + this.reset(); + } else { + if (useUTC) { + this.zone = Timezone.utcTimezone; + this.year = aDate.getUTCFullYear(); + this.month = aDate.getUTCMonth() + 1; + this.day = aDate.getUTCDate(); + this.hour = aDate.getUTCHours(); + this.minute = aDate.getUTCMinutes(); + this.second = aDate.getUTCSeconds(); + } else { + this.zone = Timezone.localTimezone; + this.year = aDate.getFullYear(); + this.month = aDate.getMonth() + 1; + this.day = aDate.getDate(); + this.hour = aDate.getHours(); + this.minute = aDate.getMinutes(); + this.second = aDate.getSeconds(); + } + } + this._cachedUnixTime = null; + return this; + } + + /** + * Sets up the current instance using members from the passed data object. + * + * @param {timeInit} aData Time initialization + * @param {Timezone=} aZone Timezone this position occurs in + */ + }, { + key: "fromData", + value: function fromData(aData, aZone) { + if (aData) { + for (var _i = 0, _Object$entries = Object.entries(aData); _i < _Object$entries.length; _i++) { + var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), + key = _Object$entries$_i[0], + value = _Object$entries$_i[1]; + // ical type cannot be set + if (key === 'icaltype') continue; + this[key] = value; + } + } + if (aZone) { + this.zone = aZone; + } + if (aData && !("isDate" in aData)) { + this.isDate = !("hour" in aData); + } else if (aData && "isDate" in aData) { + this.isDate = aData.isDate; + } + if (aData && "timezone" in aData) { + var zone = TimezoneService.get(aData.timezone); + this.zone = zone || Timezone.localTimezone; + } + if (aData && "zone" in aData) { + this.zone = aData.zone; + } + if (!this.zone) { + this.zone = Timezone.localTimezone; + } + this._cachedUnixTime = null; + return this; + } + + /** + * Calculate the day of week. + * @param {weekDay=} aWeekStart + * The week start weekday, defaults to SUNDAY + * @return {weekDay} + */ + }, { + key: "dayOfWeek", + value: function dayOfWeek(aWeekStart) { + var firstDow = aWeekStart || Time.SUNDAY; + var dowCacheKey = (this.year << 12) + (this.month << 8) + (this.day << 3) + firstDow; + if (dowCacheKey in Time._dowCache) { + return Time._dowCache[dowCacheKey]; + } + + // Using Zeller's algorithm + var q = this.day; + var m = this.month + (this.month < 3 ? 12 : 0); + var Y = this.year - (this.month < 3 ? 1 : 0); + var h = q + Y + trunc((m + 1) * 26 / 10) + trunc(Y / 4); + { + // eslint-disable-line no-constant-condition + h += trunc(Y / 100) * 6 + trunc(Y / 400); + } + + // Normalize to 1 = wkst + h = (h + 7 - firstDow) % 7 + 1; + Time._dowCache[dowCacheKey] = h; + return h; + } + + /** + * Calculate the day of year. + * @return {Number} + */ + }, { + key: "dayOfYear", + value: function dayOfYear() { + var is_leap = Time.isLeapYear(this.year) ? 1 : 0; + var diypm = Time.daysInYearPassedMonth; + return diypm[is_leap][this.month - 1] + this.day; + } + + /** + * Returns a copy of the current date/time, rewound to the start of the + * week. The resulting ICAL.Time instance is of icaltype date, even if this + * is a date-time. + * + * @param {weekDay=} aWeekStart + * The week start weekday, defaults to SUNDAY + * @return {Time} The start of the week (cloned) + */ + }, { + key: "startOfWeek", + value: function startOfWeek(aWeekStart) { + var firstDow = aWeekStart || Time.SUNDAY; + var result = this.clone(); + result.day -= (this.dayOfWeek() + 7 - firstDow) % 7; + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * Returns a copy of the current date/time, shifted to the end of the week. + * The resulting ICAL.Time instance is of icaltype date, even if this is a + * date-time. + * + * @param {weekDay=} aWeekStart + * The week start weekday, defaults to SUNDAY + * @return {Time} The end of the week (cloned) + */ + }, { + key: "endOfWeek", + value: function endOfWeek(aWeekStart) { + var firstDow = aWeekStart || Time.SUNDAY; + var result = this.clone(); + result.day += (7 - this.dayOfWeek() + firstDow - Time.SUNDAY) % 7; + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * Returns a copy of the current date/time, rewound to the start of the + * month. The resulting ICAL.Time instance is of icaltype date, even if + * this is a date-time. + * + * @return {Time} The start of the month (cloned) + */ + }, { + key: "startOfMonth", + value: function startOfMonth() { + var result = this.clone(); + result.day = 1; + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * Returns a copy of the current date/time, shifted to the end of the + * month. The resulting ICAL.Time instance is of icaltype date, even if + * this is a date-time. + * + * @return {Time} The end of the month (cloned) + */ + }, { + key: "endOfMonth", + value: function endOfMonth() { + var result = this.clone(); + result.day = Time.daysInMonth(result.month, result.year); + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * Returns a copy of the current date/time, rewound to the start of the + * year. The resulting ICAL.Time instance is of icaltype date, even if + * this is a date-time. + * + * @return {Time} The start of the year (cloned) + */ + }, { + key: "startOfYear", + value: function startOfYear() { + var result = this.clone(); + result.day = 1; + result.month = 1; + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * Returns a copy of the current date/time, shifted to the end of the + * year. The resulting ICAL.Time instance is of icaltype date, even if + * this is a date-time. + * + * @return {Time} The end of the year (cloned) + */ + }, { + key: "endOfYear", + value: function endOfYear() { + var result = this.clone(); + result.day = 31; + result.month = 12; + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * First calculates the start of the week, then returns the day of year for + * this date. If the day falls into the previous year, the day is zero or negative. + * + * @param {weekDay=} aFirstDayOfWeek + * The week start weekday, defaults to SUNDAY + * @return {Number} The calculated day of year + */ + }, { + key: "startDoyWeek", + value: function startDoyWeek(aFirstDayOfWeek) { + var firstDow = aFirstDayOfWeek || Time.SUNDAY; + var delta = this.dayOfWeek() - firstDow; + if (delta < 0) delta += 7; + return this.dayOfYear() - delta; + } + + /** + * Get the dominical letter for the current year. Letters range from A - G + * for common years, and AG to GF for leap years. + * + * @param {Number} yr The year to retrieve the letter for + * @return {String} The dominical letter. + */ + }, { + key: "getDominicalLetter", + value: function getDominicalLetter() { + return Time.getDominicalLetter(this.year); + } + + /** + * Finds the nthWeekDay relative to the current month (not day). The + * returned value is a day relative the month that this month belongs to so + * 1 would indicate the first of the month and 40 would indicate a day in + * the following month. + * + * @param {Number} aDayOfWeek Day of the week see the day name constants + * @param {Number} aPos Nth occurrence of a given week day values + * of 1 and 0 both indicate the first weekday of that type. aPos may + * be either positive or negative + * + * @return {Number} numeric value indicating a day relative + * to the current month of this time object + */ + }, { + key: "nthWeekDay", + value: function nthWeekDay(aDayOfWeek, aPos) { + var daysInMonth = Time.daysInMonth(this.month, this.year); + var weekday; + var pos = aPos; + var start = 0; + var otherDay = this.clone(); + if (pos >= 0) { + otherDay.day = 1; + + // because 0 means no position has been given + // 1 and 0 indicate the same day. + if (pos != 0) { + // remove the extra numeric value + pos--; + } + + // set current start offset to current day. + start = otherDay.day; + + // find the current day of week + var startDow = otherDay.dayOfWeek(); + + // calculate the difference between current + // day of the week and desired day of the week + var offset = aDayOfWeek - startDow; + + // if the offset goes into the past + // week we add 7 so it goes into the next + // week. We only want to go forward in time here. + if (offset < 0) + // this is really important otherwise we would + // end up with dates from in the past. + offset += 7; + + // add offset to start so start is the same + // day of the week as the desired day of week. + start += offset; + + // because we are going to add (and multiply) + // the numeric value of the day we subtract it + // from the start position so not to add it twice. + start -= aDayOfWeek; + + // set week day + weekday = aDayOfWeek; + } else { + // then we set it to the last day in the current month + otherDay.day = daysInMonth; + + // find the ends weekday + var endDow = otherDay.dayOfWeek(); + pos++; + weekday = endDow - aDayOfWeek; + if (weekday < 0) { + weekday += 7; + } + weekday = daysInMonth - weekday; + } + weekday += pos * 7; + return start + weekday; + } + + /** + * Checks if current time is the nth weekday, relative to the current + * month. Will always return false when rule resolves outside of current + * month. + * + * @param {weekDay} aDayOfWeek Day of week to check + * @param {Number} aPos Relative position + * @return {Boolean} True, if it is the nth weekday + */ + }, { + key: "isNthWeekDay", + value: function isNthWeekDay(aDayOfWeek, aPos) { + var dow = this.dayOfWeek(); + if (aPos === 0 && dow === aDayOfWeek) { + return true; + } + + // get pos + var day = this.nthWeekDay(aDayOfWeek, aPos); + if (day === this.day) { + return true; + } + return false; + } + + /** + * Calculates the ISO 8601 week number. The first week of a year is the + * week that contains the first Thursday. The year can have 53 weeks, if + * January 1st is a Friday. + * + * Note there are regions where the first week of the year is the one that + * starts on January 1st, which may offset the week number. Also, if a + * different week start is specified, this will also affect the week + * number. + * + * @see Time.weekOneStarts + * @param {weekDay} aWeekStart The weekday the week starts with + * @return {Number} The ISO week number + */ + }, { + key: "weekNumber", + value: function weekNumber(aWeekStart) { + var wnCacheKey = (this.year << 12) + (this.month << 8) + (this.day << 3) + aWeekStart; + if (wnCacheKey in Time._wnCache) { + return Time._wnCache[wnCacheKey]; + } + // This function courtesty of Julian Bucknall, published under the MIT license + // http://www.boyet.com/articles/publishedarticles/calculatingtheisoweeknumb.html + // plus some fixes to be able to use different week starts. + var week1; + var dt = this.clone(); + dt.isDate = true; + var isoyear = this.year; + if (dt.month == 12 && dt.day > 25) { + week1 = Time.weekOneStarts(isoyear + 1, aWeekStart); + if (dt.compare(week1) < 0) { + week1 = Time.weekOneStarts(isoyear, aWeekStart); + } else { + isoyear++; + } + } else { + week1 = Time.weekOneStarts(isoyear, aWeekStart); + if (dt.compare(week1) < 0) { + week1 = Time.weekOneStarts(--isoyear, aWeekStart); + } + } + var daysBetween = dt.subtractDate(week1).toSeconds() / 86400; + var answer = trunc(daysBetween / 7) + 1; + Time._wnCache[wnCacheKey] = answer; + return answer; + } + + /** + * Adds the duration to the current time. The instance is modified in + * place. + * + * @param {Duration} aDuration The duration to add + */ + }, { + key: "addDuration", + value: function addDuration(aDuration) { + var mult = aDuration.isNegative ? -1 : 1; + + // because of the duration optimizations it is much + // more efficient to grab all the values up front + // then set them directly (which will avoid a normalization call). + // So we don't actually normalize until we need it. + var second = this.second; + var minute = this.minute; + var hour = this.hour; + var day = this.day; + second += mult * aDuration.seconds; + minute += mult * aDuration.minutes; + hour += mult * aDuration.hours; + day += mult * aDuration.days; + day += mult * 7 * aDuration.weeks; + this.second = second; + this.minute = minute; + this.hour = hour; + this.day = day; + this._cachedUnixTime = null; + } + + /** + * Subtract the date details (_excluding_ timezone). Useful for finding + * the relative difference between two time objects excluding their + * timezone differences. + * + * @param {Time} aDate The date to subtract + * @return {Duration} The difference as a duration + */ + }, { + key: "subtractDate", + value: function subtractDate(aDate) { + var unixTime = this.toUnixTime() + this.utcOffset(); + var other = aDate.toUnixTime() + aDate.utcOffset(); + return Duration.fromSeconds(unixTime - other); + } + + /** + * Subtract the date details, taking timezones into account. + * + * @param {Time} aDate The date to subtract + * @return {Duration} The difference in duration + */ + }, { + key: "subtractDateTz", + value: function subtractDateTz(aDate) { + var unixTime = this.toUnixTime(); + var other = aDate.toUnixTime(); + return Duration.fromSeconds(unixTime - other); + } + + /** + * Compares the ICAL.Time instance with another one, or a period. + * + * @param {Time|Period} aOther The instance to compare with + * @return {Number} -1, 0 or 1 for less/equal/greater + */ + }, { + key: "compare", + value: function compare(other) { + if (other instanceof Period) { + return -1 * other.compare(this); + } else { + var a = this.toUnixTime(); + var b = other.toUnixTime(); + if (a > b) return 1; + if (b > a) return -1; + return 0; + } + } + + /** + * Compares only the date part of this instance with another one. + * + * @param {Time} other The instance to compare with + * @param {Timezone} tz The timezone to compare in + * @return {Number} -1, 0 or 1 for less/equal/greater + */ + }, { + key: "compareDateOnlyTz", + value: function compareDateOnlyTz(other, tz) { + var a = this.convertToZone(tz); + var b = other.convertToZone(tz); + var rc = 0; + if ((rc = Time._cmp_attr(a, b, "year")) != 0) return rc; + if ((rc = Time._cmp_attr(a, b, "month")) != 0) return rc; + if ((rc = Time._cmp_attr(a, b, "day")) != 0) return rc; + return rc; + } + + /** + * Convert the instance into another timezone. The returned ICAL.Time + * instance is always a copy. + * + * @param {Timezone} zone The zone to convert to + * @return {Time} The copy, converted to the zone + */ + }, { + key: "convertToZone", + value: function convertToZone(zone) { + var copy = this.clone(); + var zone_equals = this.zone.tzid == zone.tzid; + if (!this.isDate && !zone_equals) { + Timezone.convert_time(copy, this.zone, zone); + } + copy.zone = zone; + return copy; + } + + /** + * Calculates the UTC offset of the current date/time in the timezone it is + * in. + * + * @return {Number} UTC offset in seconds + */ + }, { + key: "utcOffset", + value: function utcOffset() { + if (this.zone == Timezone.localTimezone || this.zone == Timezone.utcTimezone) { + return 0; + } else { + return this.zone.utcOffset(this); + } + } + + /** + * Returns an RFC 5545 compliant ical representation of this object. + * + * @return {String} ical date/date-time + */ + }, { + key: "toICALString", + value: function toICALString() { + var string = this.toString(); + if (string.length > 10) { + return design.icalendar.value['date-time'].toICAL(string); + } else { + return design.icalendar.value.date.toICAL(string); + } + } + + /** + * The string representation of this date/time, in jCal form + * (including : and - separators). + * @return {String} + */ + }, { + key: "toString", + value: function toString() { + var result = this.year + '-' + pad2(this.month) + '-' + pad2(this.day); + if (!this.isDate) { + result += 'T' + pad2(this.hour) + ':' + pad2(this.minute) + ':' + pad2(this.second); + if (this.zone === Timezone.utcTimezone) { + result += 'Z'; + } + } + return result; + } + + /** + * Converts the current instance to a Javascript date + * @return {Date} + */ + }, { + key: "toJSDate", + value: function toJSDate() { + if (this.zone == Timezone.localTimezone) { + if (this.isDate) { + return new Date(this.year, this.month - 1, this.day); + } else { + return new Date(this.year, this.month - 1, this.day, this.hour, this.minute, this.second, 0); + } + } else { + return new Date(this.toUnixTime() * 1000); + } + } + }, { + key: "_normalize", + value: function _normalize() { + if (this._time.isDate) { + this._time.hour = 0; + this._time.minute = 0; + this._time.second = 0; + } + this.adjust(0, 0, 0, 0); + return this; + } + + /** + * Adjust the date/time by the given offset + * + * @param {Number} aExtraDays The extra amount of days + * @param {Number} aExtraHours The extra amount of hours + * @param {Number} aExtraMinutes The extra amount of minutes + * @param {Number} aExtraSeconds The extra amount of seconds + * @param {Number=} aTime The time to adjust, defaults to the + * current instance. + */ + }, { + key: "adjust", + value: function adjust(aExtraDays, aExtraHours, aExtraMinutes, aExtraSeconds, aTime) { + var minutesOverflow, + hoursOverflow, + daysOverflow = 0, + yearsOverflow = 0; + var second, minute, hour, day; + var daysInMonth; + var time = aTime || this._time; + if (!time.isDate) { + second = time.second + aExtraSeconds; + time.second = second % 60; + minutesOverflow = trunc(second / 60); + if (time.second < 0) { + time.second += 60; + minutesOverflow--; + } + minute = time.minute + aExtraMinutes + minutesOverflow; + time.minute = minute % 60; + hoursOverflow = trunc(minute / 60); + if (time.minute < 0) { + time.minute += 60; + hoursOverflow--; + } + hour = time.hour + aExtraHours + hoursOverflow; + time.hour = hour % 24; + daysOverflow = trunc(hour / 24); + if (time.hour < 0) { + time.hour += 24; + daysOverflow--; + } + } + + // Adjust month and year first, because we need to know what month the day + // is in before adjusting it. + if (time.month > 12) { + yearsOverflow = trunc((time.month - 1) / 12); + } else if (time.month < 1) { + yearsOverflow = trunc(time.month / 12) - 1; + } + time.year += yearsOverflow; + time.month -= 12 * yearsOverflow; + + // Now take care of the days (and adjust month if needed) + day = time.day + aExtraDays + daysOverflow; + if (day > 0) { + for (;;) { + daysInMonth = Time.daysInMonth(time.month, time.year); + if (day <= daysInMonth) { + break; + } + time.month++; + if (time.month > 12) { + time.year++; + time.month = 1; + } + day -= daysInMonth; + } + } else { + while (day <= 0) { + if (time.month == 1) { + time.year--; + time.month = 12; + } else { + time.month--; + } + day += Time.daysInMonth(time.month, time.year); + } + } + time.day = day; + this._cachedUnixTime = null; + return this; + } + + /** + * Sets up the current instance from unix time, the number of seconds since + * January 1st, 1970. + * + * @param {Number} seconds The seconds to set up with + */ + }, { + key: "fromUnixTime", + value: function fromUnixTime(seconds) { + this.zone = Timezone.utcTimezone; + // We could use `fromJSDate` here, but this is about twice as fast. + // We could also clone `epochTime` and use `adjust` for a more + // ical.js-centric approach, but this is about 100 times as fast. + var date = new Date(seconds * 1000); + this.year = date.getUTCFullYear(); + this.month = date.getUTCMonth() + 1; + this.day = date.getUTCDate(); + if (this._time.isDate) { + this.hour = 0; + this.minute = 0; + this.second = 0; + } else { + this.hour = date.getUTCHours(); + this.minute = date.getUTCMinutes(); + this.second = date.getUTCSeconds(); + } + this._cachedUnixTime = null; + } + + /** + * Converts the current instance to seconds since January 1st 1970. + * + * @return {Number} Seconds since 1970 + */ + }, { + key: "toUnixTime", + value: function toUnixTime() { + if (this._cachedUnixTime !== null) { + return this._cachedUnixTime; + } + var offset = this.utcOffset(); + + // we use the offset trick to ensure + // that we are getting the actual UTC time + var ms = Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second - offset); + + // seconds + this._cachedUnixTime = ms / 1000; + return this._cachedUnixTime; + } + + /** + * Converts time to into Object which can be serialized then re-created + * using the constructor. + * + * @example + * // toJSON will automatically be called + * var json = JSON.stringify(mytime); + * + * var deserialized = JSON.parse(json); + * + * var time = new ICAL.Time(deserialized); + * + * @return {Object} + */ + }, { + key: "toJSON", + value: function toJSON() { + var copy = ['year', 'month', 'day', 'hour', 'minute', 'second', 'isDate']; + var result = Object.create(null); + var i = 0; + var len = copy.length; + var prop; + for (; i < len; i++) { + prop = copy[i]; + result[prop] = this[prop]; + } + if (this.zone) { + result.timezone = this.zone.tzid; + } + return result; + } + }], [{ + key: "daysInMonth", + value: + /** + * Returns the days in the given month + * + * @param {Number} month The month to check + * @param {Number} year The year to check + * @return {Number} The number of days in the month + */ + function daysInMonth(month, year) { + var _daysInMonth = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + var days = 30; + if (month < 1 || month > 12) return days; + days = _daysInMonth[month]; + if (month == 2) { + days += Time.isLeapYear(year); + } + return days; + } + + /** + * Checks if the year is a leap year + * + * @param {Number} year The year to check + * @return {Boolean} True, if the year is a leap year + */ + }, { + key: "isLeapYear", + value: function isLeapYear(year) { + if (year <= 1752) { + return year % 4 == 0; + } else { + return year % 4 == 0 && year % 100 != 0 || year % 400 == 0; + } + } + + /** + * Create a new ICAL.Time from the day of year and year. The date is returned + * in floating timezone. + * + * @param {Number} aDayOfYear The day of year + * @param {Number} aYear The year to create the instance in + * @return {Time} The created instance with the calculated date + */ + }, { + key: "fromDayOfYear", + value: function fromDayOfYear(aDayOfYear, aYear) { + var year = aYear; + var doy = aDayOfYear; + var tt = new Time(); + tt.auto_normalize = false; + var is_leap = Time.isLeapYear(year) ? 1 : 0; + if (doy < 1) { + year--; + is_leap = Time.isLeapYear(year) ? 1 : 0; + doy += Time.daysInYearPassedMonth[is_leap][12]; + return Time.fromDayOfYear(doy, year); + } else if (doy > Time.daysInYearPassedMonth[is_leap][12]) { + is_leap = Time.isLeapYear(year) ? 1 : 0; + doy -= Time.daysInYearPassedMonth[is_leap][12]; + year++; + return Time.fromDayOfYear(doy, year); + } + tt.year = year; + tt.isDate = true; + for (var month = 11; month >= 0; month--) { + if (doy > Time.daysInYearPassedMonth[is_leap][month]) { + tt.month = month + 1; + tt.day = doy - Time.daysInYearPassedMonth[is_leap][month]; + break; + } + } + tt.auto_normalize = true; + return tt; + } + + /** + * Returns a new ICAL.Time instance from a date string, e.g 2015-01-02. + * + * @deprecated Use {@link ICAL.Time.fromDateString} instead + * @param {String} str The string to create from + * @return {Time} The date/time instance + */ + }, { + key: "fromStringv2", + value: function fromStringv2(str) { + return new Time({ + year: parseInt(str.slice(0, 4), 10), + month: parseInt(str.slice(5, 7), 10), + day: parseInt(str.slice(8, 10), 10), + isDate: true + }); + } + + /** + * Returns a new ICAL.Time instance from a date string, e.g 2015-01-02. + * + * @param {String} aValue The string to create from + * @return {Time} The date/time instance + */ + }, { + key: "fromDateString", + value: function fromDateString(aValue) { + // Dates should have no timezone. + // Google likes to sometimes specify Z on dates + // we specifically ignore that to avoid issues. + + // YYYY-MM-DD + // 2012-10-10 + return new Time({ + year: strictParseInt(aValue.slice(0, 4)), + month: strictParseInt(aValue.slice(5, 7)), + day: strictParseInt(aValue.slice(8, 10)), + isDate: true + }); + } + + /** + * Returns a new ICAL.Time instance from a date-time string, e.g + * 2015-01-02T03:04:05. If a property is specified, the timezone is set up + * from the property's TZID parameter. + * + * @param {String} aValue The string to create from + * @param {Property=} prop The property the date belongs to + * @return {Time} The date/time instance + */ + }, { + key: "fromDateTimeString", + value: function fromDateTimeString(aValue, prop) { + if (aValue.length < 19) { + throw new Error('invalid date-time value: "' + aValue + '"'); + } + var zone; + var zoneId; + if (aValue.slice(-1) === 'Z') { + zone = Timezone.utcTimezone; + } else if (prop) { + zoneId = prop.getParameter('tzid'); + if (prop.parent) { + if (prop.parent.name === 'standard' || prop.parent.name === 'daylight') { + // Per RFC 5545 3.8.2.4 and 3.8.2.2, start/end date-times within + // these components MUST be specified in local time. + zone = Timezone.localTimezone; + } else if (zoneId) { + // If the desired time zone is defined within the component tree, + // fetch its definition and prefer that. + zone = prop.parent.getTimeZoneByID(zoneId); + } + } + } + var timeData = { + year: strictParseInt(aValue.slice(0, 4)), + month: strictParseInt(aValue.slice(5, 7)), + day: strictParseInt(aValue.slice(8, 10)), + hour: strictParseInt(aValue.slice(11, 13)), + minute: strictParseInt(aValue.slice(14, 16)), + second: strictParseInt(aValue.slice(17, 19)) + }; + + // Although RFC 5545 requires that all TZIDs used within a file have a + // corresponding time zone definition, we may not be parsing the full file + // or we may be dealing with a non-compliant file; in either case, we can + // check our own time zone service for the TZID in a last-ditch effort. + if (zoneId && !zone) { + timeData.timezone = zoneId; + } + + // 2012-10-10T10:10:10(Z)? + return new Time(timeData, zone); + } + + /** + * Returns a new ICAL.Time instance from a date or date-time string, + * + * @param {String} aValue The string to create from + * @param {Property=} prop The property the date belongs to + * @return {Time} The date/time instance + */ + }, { + key: "fromString", + value: function fromString(aValue, aProperty) { + if (aValue.length > 10) { + return Time.fromDateTimeString(aValue, aProperty); + } else { + return Time.fromDateString(aValue); + } + } + + /** + * Creates a new ICAL.Time instance from the given Javascript Date. + * + * @param {?Date} aDate The Javascript Date to read, or null to reset + * @param {Boolean} [useUTC=false] If true, the UTC values of the date will be used + */ + }, { + key: "fromJSDate", + value: function fromJSDate(aDate, useUTC) { + var tt = new Time(); + return tt.fromJSDate(aDate, useUTC); + } + + /** + * Creates a new ICAL.Time instance from the the passed data object. + * + * @param {timeInit} aData Time initialization + * @param {Timezone=} aZone Timezone this position occurs in + */ + }, { + key: "now", + value: + /** + * Creates a new ICAL.Time instance from the current moment. + * The instance is “floating” - has no timezone relation. + * To create an instance considering the time zone, call + * ICAL.Time.fromJSDate(new Date(), true) + * @return {Time} + */ + function now() { + return Time.fromJSDate(new Date(), false); + } + + /** + * Returns the date on which ISO week number 1 starts. + * + * @see Time#weekNumber + * @param {Number} aYear The year to search in + * @param {weekDay=} aWeekStart The week start weekday, used for calculation. + * @return {Time} The date on which week number 1 starts + */ + }, { + key: "weekOneStarts", + value: function weekOneStarts(aYear, aWeekStart) { + var t = Time.fromData({ + year: aYear, + month: 1, + day: 1, + isDate: true + }); + var dow = t.dayOfWeek(); + var wkst = aWeekStart || Time.DEFAULT_WEEK_START; + if (dow > Time.THURSDAY) { + t.day += 7; + } + if (wkst > Time.THURSDAY) { + t.day -= 7; + } + t.day -= dow - wkst; + return t; + } + + /** + * Get the dominical letter for the given year. Letters range from A - G for + * common years, and AG to GF for leap years. + * + * @param {Number} yr The year to retrieve the letter for + * @return {String} The dominical letter. + */ + }, { + key: "getDominicalLetter", + value: function getDominicalLetter(yr) { + var LTRS = "GFEDCBA"; + var dom = (yr + (yr / 4 | 0) + (yr / 400 | 0) - (yr / 100 | 0) - 1) % 7; + var isLeap = Time.isLeapYear(yr); + if (isLeap) { + return LTRS[(dom + 6) % 7] + LTRS[dom]; + } else { + return LTRS[dom]; + } + } + }, { + key: "epochTime", + get: + /** + * January 1st, 1970 as an ICAL.Time. + * @type {Time} + * @constant + * @instance + */ + function get() { + if (!_assertClassBrand(Time, this, _epochTime)._) { + _epochTime._ = _assertClassBrand(Time, this, Time.fromData({ + year: 1970, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + isDate: false, + timezone: "Z" + })); + } + return _assertClassBrand(Time, this, _epochTime)._; + } + }, { + key: "_cmp_attr", + value: function _cmp_attr(a, b, attr) { + if (a[attr] > b[attr]) return 1; + if (a[attr] < b[attr]) return -1; + return 0; + } + + /** + * The days that have passed in the year after a given month. The array has + * two members, one being an array of passed days for non-leap years, the + * other analog for leap years. + * @example + * var isLeapYear = ICAL.Time.isLeapYear(year); + * var passedDays = ICAL.Time.daysInYearPassedMonth[isLeapYear][month]; + * @type {Array.>} + */ + }]); + }(); + _Time = Time; + _defineProperty(Time, "_dowCache", {}); + _defineProperty(Time, "_wnCache", {}); + _defineProperty(Time, "fromData", function fromData(aData, aZone) { + var t = new _Time(); + return t.fromData(aData, aZone); + }); + var _epochTime = { + _: null + }; + _defineProperty(Time, "daysInYearPassedMonth", [[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365], [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]]); + _defineProperty(Time, "SUNDAY", 1); + _defineProperty(Time, "MONDAY", 2); + _defineProperty(Time, "TUESDAY", 3); + _defineProperty(Time, "WEDNESDAY", 4); + _defineProperty(Time, "THURSDAY", 5); + _defineProperty(Time, "FRIDAY", 6); + _defineProperty(Time, "SATURDAY", 7); + /** + * The default weekday for the WKST part. + * @constant + * @default ICAL.Time.MONDAY + */ + _defineProperty(Time, "DEFAULT_WEEK_START", 2); + + /** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * + * @ignore + * @typedef {import("./types.js").parserState} parserState + * Imports the 'parserState' type from the "types.js" module + * @typedef {import("./types.js").designSet} designSet + * Imports the 'designSet' type from the "types.js" module + */ + + var CHAR = /[^ \t]/; + var VALUE_DELIMITER = ':'; + var PARAM_DELIMITER = ';'; + var PARAM_NAME_DELIMITER = '='; + var DEFAULT_VALUE_TYPE$1 = 'unknown'; + var DEFAULT_PARAM_TYPE = 'text'; + var RFC6868_REPLACE_MAP$1 = { + "^'": '"', + "^n": "\n", + "^^": "^" + }; + + /** + * Parses iCalendar or vCard data into a raw jCal object. Consult + * documentation on the {@tutorial layers|layers of parsing} for more + * details. + * + * @function ICAL.parse + * @memberof ICAL + * @variation function + * @todo Fix the API to be more clear on the return type + * @param {String} input The string data to parse + * @return {Object|Object[]} A single jCal object, or an array thereof + */ + function parse(input) { + var state = {}; + var root = state.component = []; + state.stack = [root]; + parse._eachLine(input, function (err, line) { + parse._handleContentLine(line, state); + }); + + // when there are still items on the stack + // throw a fatal error, a component was not closed + // correctly in that case. + if (state.stack.length > 1) { + throw new ParserError('invalid ical body. component began but did not end'); + } + state = null; + return root.length == 1 ? root[0] : root; + } + + /** + * Parse an iCalendar property value into the jCal for a single property + * + * @function ICAL.parse.property + * @param {String} str + * The iCalendar property string to parse + * @param {designSet=} designSet + * The design data to use for this property + * @return {Object} + * The jCal Object containing the property + */ + parse.property = function (str, designSet) { + var state = { + component: [[], []], + designSet: designSet || design.defaultSet + }; + parse._handleContentLine(str, state); + return state.component[1][0]; + }; + + /** + * Convenience method to parse a component. You can use ICAL.parse() directly + * instead. + * + * @function ICAL.parse.component + * @see ICAL.parse(function) + * @param {String} str The iCalendar component string to parse + * @return {Object} The jCal Object containing the component + */ + parse.component = function (str) { + return parse(str); + }; + + /** + * An error that occurred during parsing. + * + * @param {String} message The error message + * @memberof ICAL.parse + * @extends {Error} + */ + var ParserError = /*#__PURE__*/function (_Error) { + function ParserError() { + var _this; + _classCallCheck(this, ParserError); + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + _this = _callSuper(this, ParserError, [].concat(args)); + _defineProperty(_this, "name", _this.constructor.name); + return _this; + } + _inherits(ParserError, _Error); + return _createClass(ParserError); + }(/*#__PURE__*/_wrapNativeSuper(Error)); // classes & constants + parse.ParserError = ParserError; + + /** + * Handles a single line of iCalendar/vCard, updating the state. + * + * @private + * @function ICAL.parse._handleContentLine + * @param {String} line The content line to process + * @param {parserState} state The current state of the line parsing + */ + parse._handleContentLine = function (line, state) { + // break up the parts of the line + var valuePos = line.indexOf(VALUE_DELIMITER); + var paramPos = line.indexOf(PARAM_DELIMITER); + var lastParamIndex; + var lastValuePos; + + // name of property or begin/end + var name; + var value; + // params is only overridden if paramPos !== -1. + // we can't do params = params || {} later on + // because it sacrifices ops. + var params = {}; + + /** + * Different property cases + * + * + * 1. RRULE:FREQ=foo + * // FREQ= is not a param but the value + * + * 2. ATTENDEE;ROLE=REQ-PARTICIPANT; + * // ROLE= is a param because : has not happened yet + */ + // when the parameter delimiter is after the + // value delimiter then it is not a parameter. + + if (paramPos !== -1 && valuePos !== -1) { + // when the parameter delimiter is after the + // value delimiter then it is not a parameter. + if (paramPos > valuePos) { + paramPos = -1; + } + } + var parsedParams; + if (paramPos !== -1) { + name = line.slice(0, Math.max(0, paramPos)).toLowerCase(); + parsedParams = parse._parseParameters(line.slice(Math.max(0, paramPos)), 0, state.designSet); + if (parsedParams[2] == -1) { + throw new ParserError("Invalid parameters in '" + line + "'"); + } + params = parsedParams[0]; + // Handle parameter values with multiple entries + var parsedParamLength; + if (typeof parsedParams[1] === 'string') { + parsedParamLength = parsedParams[1].length; + } else { + parsedParamLength = parsedParams[1].reduce(function (accumulator, currentValue) { + return accumulator + currentValue.length; + }, 0); + } + lastParamIndex = parsedParamLength + parsedParams[2] + paramPos; + if ((lastValuePos = line.slice(Math.max(0, lastParamIndex)).indexOf(VALUE_DELIMITER)) !== -1) { + value = line.slice(Math.max(0, lastParamIndex + lastValuePos + 1)); + } else { + throw new ParserError("Missing parameter value in '" + line + "'"); + } + } else if (valuePos !== -1) { + // without parmeters (BEGIN:VCAENDAR, CLASS:PUBLIC) + name = line.slice(0, Math.max(0, valuePos)).toLowerCase(); + value = line.slice(Math.max(0, valuePos + 1)); + if (name === 'begin') { + var newComponent = [value.toLowerCase(), [], []]; + if (state.stack.length === 1) { + state.component.push(newComponent); + } else { + state.component[2].push(newComponent); + } + state.stack.push(state.component); + state.component = newComponent; + if (!state.designSet) { + state.designSet = design.getDesignSet(state.component[0]); + } + return; + } else if (name === 'end') { + state.component = state.stack.pop(); + return; + } + // If it is not begin/end, then this is a property with an empty value, + // which should be considered valid. + } else { + /** + * Invalid line. + * The rational to throw an error is we will + * never be certain that the rest of the file + * is sane and it is unlikely that we can serialize + * the result correctly either. + */ + throw new ParserError('invalid line (no token ";" or ":") "' + line + '"'); + } + var valueType; + var multiValue = false; + var structuredValue = false; + var propertyDetails; + var splitName; + var ungroupedName; + + // fetch the ungrouped part of the name + if (state.designSet.propertyGroups && name.indexOf('.') !== -1) { + splitName = name.split('.'); + params.group = splitName[0]; + ungroupedName = splitName[1]; + } else { + ungroupedName = name; + } + if (ungroupedName in state.designSet.property) { + propertyDetails = state.designSet.property[ungroupedName]; + if ('multiValue' in propertyDetails) { + multiValue = propertyDetails.multiValue; + } + if ('structuredValue' in propertyDetails) { + structuredValue = propertyDetails.structuredValue; + } + if (value && 'detectType' in propertyDetails) { + valueType = propertyDetails.detectType(value); + } + } + + // attempt to determine value + if (!valueType) { + if (!('value' in params)) { + if (propertyDetails) { + valueType = propertyDetails.defaultType; + } else { + valueType = DEFAULT_VALUE_TYPE$1; + } + } else { + // possible to avoid this? + valueType = params.value.toLowerCase(); + } + } + delete params.value; + + /** + * Note on `var result` juggling: + * + * I observed that building the array in pieces has adverse + * effects on performance, so where possible we inline the creation. + * It is a little ugly but resulted in ~2000 additional ops/sec. + */ + + var result; + if (multiValue && structuredValue) { + value = parse._parseMultiValue(value, structuredValue, valueType, [], multiValue, state.designSet, structuredValue); + result = [ungroupedName, params, valueType, value]; + } else if (multiValue) { + result = [ungroupedName, params, valueType]; + parse._parseMultiValue(value, multiValue, valueType, result, null, state.designSet, false); + } else if (structuredValue) { + value = parse._parseMultiValue(value, structuredValue, valueType, [], null, state.designSet, structuredValue); + result = [ungroupedName, params, valueType, value]; + } else { + value = parse._parseValue(value, valueType, state.designSet, false); + result = [ungroupedName, params, valueType, value]; + } + // rfc6350 requires that in vCard 4.0 the first component is the VERSION + // component with as value 4.0, note that 3.0 does not have this requirement. + if (state.component[0] === 'vcard' && state.component[1].length === 0 && !(name === 'version' && value === '4.0')) { + state.designSet = design.getDesignSet("vcard3"); + } + state.component[1].push(result); + }; + + /** + * Parse a value from the raw value into the jCard/jCal value. + * + * @private + * @function ICAL.parse._parseValue + * @param {String} value Original value + * @param {String} type Type of value + * @param {Object} designSet The design data to use for this value + * @return {Object} varies on type + */ + parse._parseValue = function (value, type, designSet, structuredValue) { + if (type in designSet.value && 'fromICAL' in designSet.value[type]) { + return designSet.value[type].fromICAL(value, structuredValue); + } + return value; + }; + + /** + * Parse parameters from a string to object. + * + * @function ICAL.parse._parseParameters + * @private + * @param {String} line A single unfolded line + * @param {Number} start Position to start looking for properties + * @param {Object} designSet The design data to use for this property + * @return {Array} Array containing key/valye pairs of parsed parameters, the + * parsed value, and the position of the last parameter found + */ + parse._parseParameters = function (line, start, designSet) { + var lastParam = start; + var pos = 0; + var delim = PARAM_NAME_DELIMITER; + var result = {}; + var name, lcname; + var value, + valuePos = -1; + var type, multiValue, mvdelim; + + // find the next '=' sign + // use lastParam and pos to find name + // check if " is used if so get value from "->" + // then increment pos to find next ; + + while (pos !== false && (pos = line.indexOf(delim, pos + 1)) !== -1) { + name = line.slice(lastParam + 1, pos); + if (name.length == 0) { + throw new ParserError("Empty parameter name in '" + line + "'"); + } + lcname = name.toLowerCase(); + mvdelim = false; + multiValue = false; + if (lcname in designSet.param && designSet.param[lcname].valueType) { + type = designSet.param[lcname].valueType; + } else { + type = DEFAULT_PARAM_TYPE; + } + if (lcname in designSet.param) { + multiValue = designSet.param[lcname].multiValue; + if (designSet.param[lcname].multiValueSeparateDQuote) { + mvdelim = parse._rfc6868Escape('"' + multiValue + '"'); + } + } + var nextChar = line[pos + 1]; + if (nextChar === '"') { + valuePos = pos + 2; + pos = line.indexOf('"', valuePos); + if (multiValue && pos != -1) { + var extendedValue = true; + while (extendedValue) { + if (line[pos + 1] == multiValue && line[pos + 2] == '"') { + pos = line.indexOf('"', pos + 3); + } else { + extendedValue = false; + } + } + } + if (pos === -1) { + throw new ParserError('invalid line (no matching double quote) "' + line + '"'); + } + value = line.slice(valuePos, pos); + lastParam = line.indexOf(PARAM_DELIMITER, pos); + var propValuePos = line.indexOf(VALUE_DELIMITER, pos); + // if either no next parameter or delimeter in property value, let's stop here + if (lastParam === -1 || propValuePos !== -1 && lastParam > propValuePos) { + pos = false; + } + } else { + valuePos = pos + 1; + + // move to next ";" + var nextPos = line.indexOf(PARAM_DELIMITER, valuePos); + var _propValuePos = line.indexOf(VALUE_DELIMITER, valuePos); + if (_propValuePos !== -1 && nextPos > _propValuePos) { + // this is a delimiter in the property value, let's stop here + nextPos = _propValuePos; + pos = false; + } else if (nextPos === -1) { + // no ";" + if (_propValuePos === -1) { + nextPos = line.length; + } else { + nextPos = _propValuePos; + } + pos = false; + } else { + lastParam = nextPos; + pos = nextPos; + } + value = line.slice(valuePos, nextPos); + } + var length_before = value.length; + value = parse._rfc6868Escape(value); + valuePos += length_before - value.length; + if (multiValue) { + var delimiter = mvdelim || multiValue; + value = parse._parseMultiValue(value, delimiter, type, [], null, designSet); + } else { + value = parse._parseValue(value, type, designSet); + } + if (multiValue && lcname in result) { + if (Array.isArray(result[lcname])) { + result[lcname].push(value); + } else { + result[lcname] = [result[lcname], value]; + } + } else { + result[lcname] = value; + } + } + return [result, value, valuePos]; + }; + + /** + * Internal helper for rfc6868. Exposing this on ICAL.parse so that + * hackers can disable the rfc6868 parsing if the really need to. + * + * @function ICAL.parse._rfc6868Escape + * @param {String} val The value to escape + * @return {String} The escaped value + */ + parse._rfc6868Escape = function (val) { + return val.replace(/\^['n^]/g, function (x) { + return RFC6868_REPLACE_MAP$1[x]; + }); + }; + + /** + * Parse a multi value string. This function is used either for parsing + * actual multi-value property's values, or for handling parameter values. It + * can be used for both multi-value properties and structured value properties. + * + * @private + * @function ICAL.parse._parseMultiValue + * @param {String} buffer The buffer containing the full value + * @param {String} delim The multi-value delimiter + * @param {String} type The value type to be parsed + * @param {Array.} result The array to append results to, varies on value type + * @param {String} innerMulti The inner delimiter to split each value with + * @param {designSet} designSet The design data for this value + * @return {?|Array.} Either an array of results, or the first result + */ + parse._parseMultiValue = function (buffer, delim, type, result, innerMulti, designSet, structuredValue) { + var pos = 0; + var lastPos = 0; + var value; + if (delim.length === 0) { + return buffer; + } + + // split each piece + while ((pos = unescapedIndexOf(buffer, delim, lastPos)) !== -1) { + value = buffer.slice(lastPos, pos); + if (innerMulti) { + value = parse._parseMultiValue(value, innerMulti, type, [], null, designSet, structuredValue); + } else { + value = parse._parseValue(value, type, designSet, structuredValue); + } + result.push(value); + lastPos = pos + delim.length; + } + + // on the last piece take the rest of string + value = buffer.slice(lastPos); + if (innerMulti) { + value = parse._parseMultiValue(value, innerMulti, type, [], null, designSet, structuredValue); + } else { + value = parse._parseValue(value, type, designSet, structuredValue); + } + result.push(value); + return result.length == 1 ? result[0] : result; + }; + + /** + * Process a complete buffer of iCalendar/vCard data line by line, correctly + * unfolding content. Each line will be processed with the given callback + * + * @private + * @function ICAL.parse._eachLine + * @param {String} buffer The buffer to process + * @param {function(?String, String)} callback The callback for each line + */ + parse._eachLine = function (buffer, callback) { + var len = buffer.length; + var lastPos = buffer.search(CHAR); + var pos = lastPos; + var line; + var firstChar; + var newlineOffset; + do { + pos = buffer.indexOf('\n', lastPos) + 1; + if (pos > 1 && buffer[pos - 2] === '\r') { + newlineOffset = 2; + } else { + newlineOffset = 1; + } + if (pos === 0) { + pos = len; + newlineOffset = 0; + } + firstChar = buffer[lastPos]; + if (firstChar === ' ' || firstChar === '\t') { + // add to line + line += buffer.slice(lastPos + 1, pos - newlineOffset); + } else { + if (line) callback(null, line); + // push line + line = buffer.slice(lastPos, pos - newlineOffset); + } + lastPos = pos; + } while (pos !== len); + + // extra ending line + line = line.trim(); + if (line.length) callback(null, line); + }; + + var OPTIONS = ["tzid", "location", "tznames", "latitude", "longitude"]; + + /** + * Timezone representation. + * + * @example + * var vcalendar; + * var timezoneComp = vcalendar.getFirstSubcomponent('vtimezone'); + * var tzid = timezoneComp.getFirstPropertyValue('tzid'); + * + * var timezone = new ICAL.Timezone({ + * component: timezoneComp, + * tzid + * }); + * + * @memberof ICAL + */ + var Timezone = /*#__PURE__*/function () { + /** + * Creates a new ICAL.Timezone instance, by passing in a tzid and component. + * + * @param {Component|Object} data options for class + * @param {String|Component} data.component + * If data is a simple object, then this member can be set to either a + * string containing the component data, or an already parsed + * ICAL.Component + * @param {String} data.tzid The timezone identifier + * @param {String} data.location The timezone locationw + * @param {String} data.tznames An alternative string representation of the + * timezone + * @param {Number} data.latitude The latitude of the timezone + * @param {Number} data.longitude The longitude of the timezone + */ + function Timezone(data) { + _classCallCheck(this, Timezone); + /** + * Timezone identifier + * @type {String} + */ + _defineProperty(this, "tzid", ""); + /** + * Timezone location + * @type {String} + */ + _defineProperty(this, "location", ""); + /** + * Alternative timezone name, for the string representation + * @type {String} + */ + _defineProperty(this, "tznames", ""); + /** + * The primary latitude for the timezone. + * @type {Number} + */ + _defineProperty(this, "latitude", 0.0); + /** + * The primary longitude for the timezone. + * @type {Number} + */ + _defineProperty(this, "longitude", 0.0); + /** + * The vtimezone component for this timezone. + * @type {Component} + */ + _defineProperty(this, "component", null); + /** + * The year this timezone has been expanded to. All timezone transition + * dates until this year are known and can be used for calculation + * + * @private + * @type {Number} + */ + _defineProperty(this, "expandedUntilYear", 0); + /** + * The class identifier. + * @constant + * @type {String} + * @default "icaltimezone" + */ + _defineProperty(this, "icalclass", "icaltimezone"); + this.wrappedJSObject = this; + this.fromData(data); + } + return _createClass(Timezone, [{ + key: "fromData", + value: + /** + * Sets up the current instance using members from the passed data object. + * + * @param {Component|Object} aData options for class + * @param {String|Component} aData.component + * If aData is a simple object, then this member can be set to either a + * string containing the component data, or an already parsed + * ICAL.Component + * @param {String} aData.tzid The timezone identifier + * @param {String} aData.location The timezone locationw + * @param {String} aData.tznames An alternative string representation of the + * timezone + * @param {Number} aData.latitude The latitude of the timezone + * @param {Number} aData.longitude The longitude of the timezone + */ + function fromData(aData) { + this.expandedUntilYear = 0; + this.changes = []; + if (aData instanceof Component) { + // Either a component is passed directly + this.component = aData; + } else { + // Otherwise the component may be in the data object + if (aData && "component" in aData) { + if (typeof aData.component == "string") { + // If a string was passed, parse it as a component + var jCal = parse(aData.component); + this.component = new Component(jCal); + } else if (aData.component instanceof Component) { + // If it was a component already, then just set it + this.component = aData.component; + } else { + // Otherwise just null out the component + this.component = null; + } + } + + // Copy remaining passed properties + for (var _i = 0, _OPTIONS = OPTIONS; _i < _OPTIONS.length; _i++) { + var prop = _OPTIONS[_i]; + if (aData && prop in aData) { + this[prop] = aData[prop]; + } + } + } + + // If we have a component but no TZID, attempt to get it from the + // component's properties. + if (this.component instanceof Component && !this.tzid) { + this.tzid = this.component.getFirstPropertyValue('tzid'); + } + return this; + } + + /** + * Finds the utcOffset the given time would occur in this timezone. + * + * @param {Time} tt The time to check for + * @return {Number} utc offset in seconds + */ + }, { + key: "utcOffset", + value: function utcOffset(tt) { + if (this == Timezone.utcTimezone || this == Timezone.localTimezone) { + return 0; + } + this._ensureCoverage(tt.year); + if (!this.changes.length) { + return 0; + } + var tt_change = { + year: tt.year, + month: tt.month, + day: tt.day, + hour: tt.hour, + minute: tt.minute, + second: tt.second + }; + var change_num = this._findNearbyChange(tt_change); + var change_num_to_use = -1; + var step = 1; + + // TODO: replace with bin search? + for (;;) { + var change = clone(this.changes[change_num], true); + if (change.utcOffset < change.prevUtcOffset) { + Timezone.adjust_change(change, 0, 0, 0, change.utcOffset); + } else { + Timezone.adjust_change(change, 0, 0, 0, change.prevUtcOffset); + } + var cmp = Timezone._compare_change_fn(tt_change, change); + if (cmp >= 0) { + change_num_to_use = change_num; + } else { + step = -1; + } + if (step == -1 && change_num_to_use != -1) { + break; + } + change_num += step; + if (change_num < 0) { + return 0; + } + if (change_num >= this.changes.length) { + break; + } + } + var zone_change = this.changes[change_num_to_use]; + var utcOffset_change = zone_change.utcOffset - zone_change.prevUtcOffset; + if (utcOffset_change < 0 && change_num_to_use > 0) { + var tmp_change = clone(zone_change, true); + Timezone.adjust_change(tmp_change, 0, 0, 0, tmp_change.prevUtcOffset); + if (Timezone._compare_change_fn(tt_change, tmp_change) < 0) { + var prev_zone_change = this.changes[change_num_to_use - 1]; + var want_daylight = false; // TODO + + if (zone_change.is_daylight != want_daylight && prev_zone_change.is_daylight == want_daylight) { + zone_change = prev_zone_change; + } + } + } + + // TODO return is_daylight? + return zone_change.utcOffset; + } + }, { + key: "_findNearbyChange", + value: function _findNearbyChange(change) { + // find the closest match + var idx = binsearchInsert(this.changes, change, Timezone._compare_change_fn); + if (idx >= this.changes.length) { + return this.changes.length - 1; + } + return idx; + } + }, { + key: "_ensureCoverage", + value: function _ensureCoverage(aYear) { + if (Timezone._minimumExpansionYear == -1) { + var today = Time.now(); + Timezone._minimumExpansionYear = today.year; + } + var changesEndYear = aYear; + if (changesEndYear < Timezone._minimumExpansionYear) { + changesEndYear = Timezone._minimumExpansionYear; + } + changesEndYear += Timezone.EXTRA_COVERAGE; + if (!this.changes.length || this.expandedUntilYear < aYear) { + var subcomps = this.component.getAllSubcomponents(); + var compLen = subcomps.length; + var compIdx = 0; + for (; compIdx < compLen; compIdx++) { + this._expandComponent(subcomps[compIdx], changesEndYear, this.changes); + } + this.changes.sort(Timezone._compare_change_fn); + this.expandedUntilYear = changesEndYear; + } + } + }, { + key: "_expandComponent", + value: function _expandComponent(aComponent, aYear, changes) { + if (!aComponent.hasProperty("dtstart") || !aComponent.hasProperty("tzoffsetto") || !aComponent.hasProperty("tzoffsetfrom")) { + return null; + } + var dtstart = aComponent.getFirstProperty("dtstart").getFirstValue(); + var change; + function convert_tzoffset(offset) { + return offset.factor * (offset.hours * 3600 + offset.minutes * 60); + } + function init_changes() { + var changebase = {}; + changebase.is_daylight = aComponent.name == "daylight"; + changebase.utcOffset = convert_tzoffset(aComponent.getFirstProperty("tzoffsetto").getFirstValue()); + changebase.prevUtcOffset = convert_tzoffset(aComponent.getFirstProperty("tzoffsetfrom").getFirstValue()); + return changebase; + } + if (!aComponent.hasProperty("rrule") && !aComponent.hasProperty("rdate")) { + change = init_changes(); + change.year = dtstart.year; + change.month = dtstart.month; + change.day = dtstart.day; + change.hour = dtstart.hour; + change.minute = dtstart.minute; + change.second = dtstart.second; + Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset); + changes.push(change); + } else { + var props = aComponent.getAllProperties("rdate"); + var _iterator = _createForOfIteratorHelper(props), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var rdate = _step.value; + var time = rdate.getFirstValue(); + change = init_changes(); + change.year = time.year; + change.month = time.month; + change.day = time.day; + if (time.isDate) { + change.hour = dtstart.hour; + change.minute = dtstart.minute; + change.second = dtstart.second; + if (dtstart.zone != Timezone.utcTimezone) { + Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset); + } + } else { + change.hour = time.hour; + change.minute = time.minute; + change.second = time.second; + if (time.zone != Timezone.utcTimezone) { + Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset); + } + } + changes.push(change); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + var rrule = aComponent.getFirstProperty("rrule"); + if (rrule) { + rrule = rrule.getFirstValue(); + change = init_changes(); + if (rrule.until && rrule.until.zone == Timezone.utcTimezone) { + rrule.until.adjust(0, 0, 0, change.prevUtcOffset); + rrule.until.zone = Timezone.localTimezone; + } + var iterator = rrule.iterator(dtstart); + var occ; + while (occ = iterator.next()) { + change = init_changes(); + if (occ.year > aYear || !occ) { + break; + } + change.year = occ.year; + change.month = occ.month; + change.day = occ.day; + change.hour = occ.hour; + change.minute = occ.minute; + change.second = occ.second; + change.isDate = occ.isDate; + Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset); + changes.push(change); + } + } + } + return changes; + } + + /** + * The string representation of this timezone. + * @return {String} + */ + }, { + key: "toString", + value: function toString() { + return this.tznames ? this.tznames : this.tzid; + } + }], [{ + key: "_compare_change_fn", + value: function _compare_change_fn(a, b) { + if (a.year < b.year) return -1;else if (a.year > b.year) return 1; + if (a.month < b.month) return -1;else if (a.month > b.month) return 1; + if (a.day < b.day) return -1;else if (a.day > b.day) return 1; + if (a.hour < b.hour) return -1;else if (a.hour > b.hour) return 1; + if (a.minute < b.minute) return -1;else if (a.minute > b.minute) return 1; + if (a.second < b.second) return -1;else if (a.second > b.second) return 1; + return 0; + } + + /** + * Convert the date/time from one zone to the next. + * + * @param {Time} tt The time to convert + * @param {Timezone} from_zone The source zone to convert from + * @param {Timezone} to_zone The target zone to convert to + * @return {Time} The converted date/time object + */ + }, { + key: "convert_time", + value: function convert_time(tt, from_zone, to_zone) { + if (tt.isDate || from_zone.tzid == to_zone.tzid || from_zone == Timezone.localTimezone || to_zone == Timezone.localTimezone) { + tt.zone = to_zone; + return tt; + } + var utcOffset = from_zone.utcOffset(tt); + tt.adjust(0, 0, 0, -utcOffset); + utcOffset = to_zone.utcOffset(tt); + tt.adjust(0, 0, 0, utcOffset); + return null; + } + + /** + * Creates a new ICAL.Timezone instance from the passed data object. + * + * @param {Component|Object} aData options for class + * @param {String|Component} aData.component + * If aData is a simple object, then this member can be set to either a + * string containing the component data, or an already parsed + * ICAL.Component + * @param {String} aData.tzid The timezone identifier + * @param {String} aData.location The timezone locationw + * @param {String} aData.tznames An alternative string representation of the + * timezone + * @param {Number} aData.latitude The latitude of the timezone + * @param {Number} aData.longitude The longitude of the timezone + */ + }, { + key: "fromData", + value: function fromData(aData) { + var tt = new Timezone(); + return tt.fromData(aData); + } + + /** + * The instance describing the UTC timezone + * @type {Timezone} + * @constant + * @instance + */ + }, { + key: "utcTimezone", + get: function get() { + if (!_assertClassBrand(Timezone, this, _utcTimezone)._) { + _utcTimezone._ = _assertClassBrand(Timezone, this, Timezone.fromData({ + tzid: "UTC" + })); + } + return _assertClassBrand(Timezone, this, _utcTimezone)._; + } + + /** + * The instance describing the local timezone + * @type {Timezone} + * @constant + * @instance + */ + }, { + key: "localTimezone", + get: function get() { + if (!_assertClassBrand(Timezone, this, _localTimezone)._) { + _localTimezone._ = _assertClassBrand(Timezone, this, Timezone.fromData({ + tzid: "floating" + })); + } + return _assertClassBrand(Timezone, this, _localTimezone)._; + } + + /** + * Adjust a timezone change object. + * @private + * @param {Object} change The timezone change object + * @param {Number} days The extra amount of days + * @param {Number} hours The extra amount of hours + * @param {Number} minutes The extra amount of minutes + * @param {Number} seconds The extra amount of seconds + */ + }, { + key: "adjust_change", + value: function adjust_change(change, days, hours, minutes, seconds) { + return Time.prototype.adjust.call(change, days, hours, minutes, seconds, change); + } + }]); + }(); + var _utcTimezone = { + _: null + }; + var _localTimezone = { + _: null + }; + _defineProperty(Timezone, "_minimumExpansionYear", -1); + _defineProperty(Timezone, "EXTRA_COVERAGE", 5); + + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + + var zones = null; + + /** + * @classdesc + * Singleton class to contain timezones. Right now it is all manual registry in + * the future we may use this class to download timezone information or handle + * loading pre-expanded timezones. + * + * @exports module:ICAL.TimezoneService + * @memberof ICAL + */ + var TimezoneService = { + get count() { + if (zones === null) { + return 0; + } + return Object.keys(zones).length; + }, + reset: function reset() { + zones = Object.create(null); + var utc = Timezone.utcTimezone; + zones.Z = utc; + zones.UTC = utc; + zones.GMT = utc; + }, + _hard_reset: function _hard_reset() { + zones = null; + }, + /** + * Checks if timezone id has been registered. + * + * @param {String} tzid Timezone identifier (e.g. America/Los_Angeles) + * @return {Boolean} False, when not present + */ + has: function has(tzid) { + if (zones === null) { + return false; + } + return !!zones[tzid]; + }, + /** + * Returns a timezone by its tzid if present. + * + * @param {String} tzid Timezone identifier (e.g. America/Los_Angeles) + * @return {Timezone | undefined} The timezone, or undefined if not found + */ + get: function get(tzid) { + if (zones === null) { + this.reset(); + } + return zones[tzid]; + }, + /** + * Registers a timezone object or component. + * + * @param {Component|Timezone} timezone + * The initialized zone or vtimezone. + * + * @param {String=} name + * The name of the timezone. Defaults to the component's TZID if not + * passed. + */ + register: function register(timezone, name) { + if (zones === null) { + this.reset(); + } + + // This avoids a breaking change by the change of argument order + // TODO remove in v3 + if (typeof timezone === "string" && name instanceof Timezone) { + var _ref = [name, timezone]; + timezone = _ref[0]; + name = _ref[1]; + } + if (!name) { + if (timezone instanceof Timezone) { + name = timezone.tzid; + } else { + if (timezone.name === 'vtimezone') { + timezone = new Timezone(timezone); + name = timezone.tzid; + } + } + } + if (!name) { + throw new TypeError("Neither a timezone nor a name was passed"); + } + if (timezone instanceof Timezone) { + zones[name] = timezone; + } else { + throw new TypeError('timezone must be ICAL.Timezone or ICAL.Component'); + } + }, + /** + * Removes a timezone by its tzid from the list. + * + * @param {String} tzid Timezone identifier (e.g. America/Los_Angeles) + * @return {?Timezone} The removed timezone, or null if not registered + */ + remove: function remove(tzid) { + if (zones === null) { + return null; + } + return delete zones[tzid]; + } + }; + + /** + * Helper functions used in various places within ical.js + * @module ICAL.helpers + */ + + /** + * Compiles a list of all referenced TZIDs in all subcomponents and + * removes any extra VTIMEZONE subcomponents. In addition, if any TZIDs + * are referenced by a component, but a VTIMEZONE does not exist, + * an attempt will be made to generate a VTIMEZONE using ICAL.TimezoneService. + * + * @param {Component} vcal The top-level VCALENDAR component. + * @return {Component} The ICAL.Component that was passed in. + */ + function updateTimezones(vcal) { + var allsubs, properties, vtimezones, reqTzid, i; + if (!vcal || vcal.name !== "vcalendar") { + //not a top-level vcalendar component + return vcal; + } + + //Store vtimezone subcomponents in an object reference by tzid. + //Store properties from everything else in another array + allsubs = vcal.getAllSubcomponents(); + properties = []; + vtimezones = {}; + for (i = 0; i < allsubs.length; i++) { + if (allsubs[i].name === "vtimezone") { + var tzid = allsubs[i].getFirstProperty("tzid").getFirstValue(); + vtimezones[tzid] = allsubs[i]; + } else { + properties = properties.concat(allsubs[i].getAllProperties()); + } + } + + //create an object with one entry for each required tz + reqTzid = {}; + for (i = 0; i < properties.length; i++) { + var _tzid = properties[i].getParameter("tzid"); + if (_tzid) { + reqTzid[_tzid] = true; + } + } + + //delete any vtimezones that are not on the reqTzid list. + for (var _i = 0, _Object$entries = Object.entries(vtimezones); _i < _Object$entries.length; _i++) { + var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), + _tzid2 = _Object$entries$_i[0], + comp = _Object$entries$_i[1]; + if (!reqTzid[_tzid2]) { + vcal.removeSubcomponent(comp); + } + } + + //create any missing, but registered timezones + for (var _i2 = 0, _Object$keys = Object.keys(reqTzid); _i2 < _Object$keys.length; _i2++) { + var _tzid3 = _Object$keys[_i2]; + if (!vtimezones[_tzid3] && TimezoneService.has(_tzid3)) { + vcal.addSubcomponent(TimezoneService.get(_tzid3).component); + } + } + return vcal; + } + + /** + * Checks if the given type is of the number type and also NaN. + * + * @param {Number} number The number to check + * @return {Boolean} True, if the number is strictly NaN + */ + function isStrictlyNaN(number) { + return typeof number === 'number' && isNaN(number); + } + + /** + * Parses a string value that is expected to be an integer, when the valid is + * not an integer throws a decoration error. + * + * @param {String} string Raw string input + * @return {Number} Parsed integer + */ + function strictParseInt(string) { + var result = parseInt(string, 10); + if (isStrictlyNaN(result)) { + throw new Error('Could not extract integer from "' + string + '"'); + } + return result; + } + + /** + * Creates or returns a class instance of a given type with the initialization + * data if the data is not already an instance of the given type. + * + * @example + * var time = new ICAL.Time(...); + * var result = ICAL.helpers.formatClassType(time, ICAL.Time); + * + * (result instanceof ICAL.Time) + * // => true + * + * result = ICAL.helpers.formatClassType({}, ICAL.Time); + * (result isntanceof ICAL.Time) + * // => true + * + * + * @param {Object} data object initialization data + * @param {Object} type object type (like ICAL.Time) + * @return {?} An instance of the found type. + */ + function formatClassType(data, type) { + if (typeof data === 'undefined') { + return undefined; + } + if (data instanceof type) { + return data; + } + return new type(data); + } + + /** + * Identical to indexOf but will only match values when they are not preceded + * by a backslash character. + * + * @param {String} buffer String to search + * @param {String} search Value to look for + * @param {Number} pos Start position + * @return {Number} The position, or -1 if not found + */ + function unescapedIndexOf(buffer, search, pos) { + while ((pos = buffer.indexOf(search, pos)) !== -1) { + if (pos > 0 && buffer[pos - 1] === '\\') { + pos += 1; + } else { + return pos; + } + } + return -1; + } + + /** + * Find the index for insertion using binary search. + * + * @param {Array} list The list to search + * @param {?} seekVal The value to insert + * @param {function(?,?)} cmpfunc The comparison func, that can + * compare two seekVals + * @return {Number} The insert position + */ + function binsearchInsert(list, seekVal, cmpfunc) { + if (!list.length) return 0; + var low = 0, + high = list.length - 1, + mid, + cmpval; + while (low <= high) { + mid = low + Math.floor((high - low) / 2); + cmpval = cmpfunc(seekVal, list[mid]); + if (cmpval < 0) high = mid - 1;else if (cmpval > 0) low = mid + 1;else break; + } + if (cmpval < 0) return mid; // insertion is displacing, so use mid outright. + else if (cmpval > 0) return mid + 1;else return mid; + } + + /** + * Clone the passed object or primitive. By default a shallow clone will be + * executed. + * + * @param {*} aSrc The thing to clone + * @param {Boolean=} aDeep If true, a deep clone will be performed + * @return {*} The copy of the thing + */ + function clone(aSrc, aDeep) { + if (!aSrc || _typeof(aSrc) != "object") { + return aSrc; + } else if (aSrc instanceof Date) { + return new Date(aSrc.getTime()); + } else if ("clone" in aSrc) { + return aSrc.clone(); + } else if (Array.isArray(aSrc)) { + var arr = []; + for (var i = 0; i < aSrc.length; i++) { + arr.push(aDeep ? clone(aSrc[i], true) : aSrc[i]); + } + return arr; + } else { + var obj = {}; + for (var _i3 = 0, _Object$entries2 = Object.entries(aSrc); _i3 < _Object$entries2.length; _i3++) { + var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i3], 2), + name = _Object$entries2$_i[0], + value = _Object$entries2$_i[1]; + if (aDeep) { + obj[name] = clone(value, true); + } else { + obj[name] = value; + } + } + return obj; + } + } + + /** + * Performs iCalendar line folding. A line ending character is inserted and + * the next line begins with a whitespace. + * + * @example + * SUMMARY:This line will be fold + * ed right in the middle of a word. + * + * @param {String} aLine The line to fold + * @return {String} The folded line + */ + function foldline(aLine) { + var result = ""; + var line = aLine || "", + pos = 0, + line_length = 0; + //pos counts position in line for the UTF-16 presentation + //line_length counts the bytes for the UTF-8 presentation + while (line.length) { + var cp = line.codePointAt(pos); + if (cp < 128) ++line_length;else if (cp < 2048) line_length += 2; //needs 2 UTF-8 bytes + else if (cp < 65536) line_length += 3;else line_length += 4; //cp is less than 1114112 + if (line_length < ICALmodule.foldLength + 1) pos += cp > 65535 ? 2 : 1;else { + result += ICALmodule.newLineChar + " " + line.slice(0, Math.max(0, pos)); + line = line.slice(Math.max(0, pos)); + pos = line_length = 0; + } + } + return result.slice(ICALmodule.newLineChar.length + 1); + } + + /** + * Pads the given string or number with zeros so it will have at least two + * characters. + * + * @param {String|Number} data The string or number to pad + * @return {String} The number padded as a string + */ + function pad2(data) { + if (typeof data !== 'string') { + // handle fractions. + if (typeof data === 'number') { + data = parseInt(data); + } + data = String(data); + } + var len = data.length; + switch (len) { + case 0: + return '00'; + case 1: + return '0' + data; + default: + return data; + } + } + + /** + * Truncates the given number, correctly handling negative numbers. + * + * @param {Number} number The number to truncate + * @return {Number} The truncated number + */ + function trunc(number) { + return number < 0 ? Math.ceil(number) : Math.floor(number); + } + + /** + * Poor-man's cross-browser object extension. Doesn't support all the + * features, but enough for our usage. Note that the target's properties are + * not overwritten with the source properties. + * + * @example + * var child = ICAL.helpers.extend(parent, { + * "bar": 123 + * }); + * + * @param {Object} source The object to extend + * @param {Object} target The object to extend with + * @return {Object} Returns the target. + */ + function extend(source, target) { + for (var key in source) { + var descr = Object.getOwnPropertyDescriptor(source, key); + if (descr && !Object.getOwnPropertyDescriptor(target, key)) { + Object.defineProperty(target, key, descr); + } + } + return target; + } + + var helpers = /*#__PURE__*/Object.freeze({ + __proto__: null, + binsearchInsert: binsearchInsert, + clone: clone, + extend: extend, + foldline: foldline, + formatClassType: formatClassType, + isStrictlyNaN: isStrictlyNaN, + pad2: pad2, + strictParseInt: strictParseInt, + trunc: trunc, + unescapedIndexOf: unescapedIndexOf, + updateTimezones: updateTimezones + }); + + /** + * This class represents the "utc-offset" value type, with various calculation and manipulation + * methods. + * + * @memberof ICAL + */ + var UtcOffset = /*#__PURE__*/function () { + /** + * Creates a new ICAL.UtcOffset instance. + * + * @param {Object} aData An object with members of the utc offset + * @param {Number=} aData.hours The hours for the utc offset + * @param {Number=} aData.minutes The minutes in the utc offset + * @param {Number=} aData.factor The factor for the utc-offset, either -1 or 1 + */ + function UtcOffset(aData) { + _classCallCheck(this, UtcOffset); + /** + * The hours in the utc-offset + * @type {Number} + */ + _defineProperty(this, "hours", 0); + /** + * The minutes in the utc-offset + * @type {Number} + */ + _defineProperty(this, "minutes", 0); + /** + * The sign of the utc offset, 1 for positive offset, -1 for negative + * offsets. + * @type {Number} + */ + _defineProperty(this, "factor", 1); + /** + * The type name, to be used in the jCal object. + * @constant + * @type {String} + * @default "utc-offset" + */ + _defineProperty(this, "icaltype", "utc-offset"); + this.fromData(aData); + } + return _createClass(UtcOffset, [{ + key: "clone", + value: + /** + * Returns a clone of the utc offset object. + * + * @return {UtcOffset} The cloned object + */ + function clone() { + return UtcOffset.fromSeconds(this.toSeconds()); + } + + /** + * Sets up the current instance using members from the passed data object. + * + * @param {Object} aData An object with members of the utc offset + * @param {Number=} aData.hours The hours for the utc offset + * @param {Number=} aData.minutes The minutes in the utc offset + * @param {Number=} aData.factor The factor for the utc-offset, either -1 or 1 + */ + }, { + key: "fromData", + value: function fromData(aData) { + if (aData) { + for (var _i = 0, _Object$entries = Object.entries(aData); _i < _Object$entries.length; _i++) { + var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), + key = _Object$entries$_i[0], + value = _Object$entries$_i[1]; + this[key] = value; + } + } + this._normalize(); + } + + /** + * Sets up the current instance from the given seconds value. The seconds + * value is truncated to the minute. Offsets are wrapped when the world + * ends, the hour after UTC+14:00 is UTC-12:00. + * + * @param {Number} aSeconds The seconds to convert into an offset + */ + }, { + key: "fromSeconds", + value: function fromSeconds(aSeconds) { + var secs = Math.abs(aSeconds); + this.factor = aSeconds < 0 ? -1 : 1; + this.hours = trunc(secs / 3600); + secs -= this.hours * 3600; + this.minutes = trunc(secs / 60); + return this; + } + + /** + * Convert the current offset to a value in seconds + * + * @return {Number} The offset in seconds + */ + }, { + key: "toSeconds", + value: function toSeconds() { + return this.factor * (60 * this.minutes + 3600 * this.hours); + } + + /** + * Compare this utc offset with another one. + * + * @param {UtcOffset} other The other offset to compare with + * @return {Number} -1, 0 or 1 for less/equal/greater + */ + }, { + key: "compare", + value: function compare(other) { + var a = this.toSeconds(); + var b = other.toSeconds(); + return (a > b) - (b > a); + } + }, { + key: "_normalize", + value: function _normalize() { + // Range: 97200 seconds (with 1 hour inbetween) + var secs = this.toSeconds(); + var factor = this.factor; + while (secs < -43200) { + // = UTC-12:00 + secs += 97200; + } + while (secs > 50400) { + // = UTC+14:00 + secs -= 97200; + } + this.fromSeconds(secs); + + // Avoid changing the factor when on zero seconds + if (secs == 0) { + this.factor = factor; + } + } + + /** + * The iCalendar string representation of this utc-offset. + * @return {String} + */ + }, { + key: "toICALString", + value: function toICALString() { + return design.icalendar.value['utc-offset'].toICAL(this.toString()); + } + + /** + * The string representation of this utc-offset. + * @return {String} + */ + }, { + key: "toString", + value: function toString() { + return (this.factor == 1 ? "+" : "-") + pad2(this.hours) + ':' + pad2(this.minutes); + } + }], [{ + key: "fromString", + value: + /** + * Creates a new {@link ICAL.UtcOffset} instance from the passed string. + * + * @param {String} aString The string to parse + * @return {Duration} The created utc-offset instance + */ + function fromString(aString) { + // -05:00 + var options = {}; + //TODO: support seconds per rfc5545 ? + options.factor = aString[0] === '+' ? 1 : -1; + options.hours = strictParseInt(aString.slice(1, 3)); + options.minutes = strictParseInt(aString.slice(4, 6)); + return new UtcOffset(options); + } + + /** + * Creates a new {@link ICAL.UtcOffset} instance from the passed seconds + * value. + * + * @param {Number} aSeconds The number of seconds to convert + */ + }, { + key: "fromSeconds", + value: function fromSeconds(aSeconds) { + var instance = new UtcOffset(); + instance.fromSeconds(aSeconds); + return instance; + } + }]); + }(); + + /** + * Describes a vCard time, which has slight differences to the ICAL.Time. + * Properties can be null if not specified, for example for dates with + * reduced accuracy or truncation. + * + * Note that currently not all methods are correctly re-implemented for + * VCardTime. For example, comparison will have undefined results when some + * members are null. + * + * Also, normalization is not yet implemented for this class! + * + * @memberof ICAL + * @extends {Time} + */ + var VCardTime = /*#__PURE__*/function (_Time) { + /** + * Creates a new ICAL.VCardTime instance. + * + * @param {Object} data The data for the time instance + * @param {Number=} data.year The year for this date + * @param {Number=} data.month The month for this date + * @param {Number=} data.day The day for this date + * @param {Number=} data.hour The hour for this date + * @param {Number=} data.minute The minute for this date + * @param {Number=} data.second The second for this date + * @param {Timezone|UtcOffset} zone The timezone to use + * @param {String} icaltype The type for this date/time object + */ + function VCardTime(data, zone, icaltype) { + var _this; + _classCallCheck(this, VCardTime); + _this = _callSuper(this, VCardTime, [data, zone]); + /** + * The class identifier. + * @constant + * @type {String} + * @default "vcardtime" + */ + _defineProperty(_this, "icalclass", "vcardtime"); + /** + * The type name, to be used in the jCal object. + * @type {String} + * @default "date-and-or-time" + */ + _defineProperty(_this, "icaltype", "date-and-or-time"); + _this.icaltype = icaltype || "date-and-or-time"; + return _this; + } + _inherits(VCardTime, _Time); + return _createClass(VCardTime, [{ + key: "clone", + value: + /** + * Returns a clone of the vcard date/time object. + * + * @return {VCardTime} The cloned object + */ + function clone() { + return new VCardTime(this._time, this.zone, this.icaltype); + } + }, { + key: "_normalize", + value: function _normalize() { + return this; + } + + /** + * @inheritdoc + */ + }, { + key: "utcOffset", + value: function utcOffset() { + if (this.zone instanceof UtcOffset) { + return this.zone.toSeconds(); + } else { + return Time.prototype.utcOffset.apply(this, arguments); + } + } + + /** + * Returns an RFC 6350 compliant representation of this object. + * + * @return {String} vcard date/time string + */ + }, { + key: "toICALString", + value: function toICALString() { + return design.vcard.value[this.icaltype].toICAL(this.toString()); + } + + /** + * The string representation of this date/time, in jCard form + * (including : and - separators). + * @return {String} + */ + }, { + key: "toString", + value: function toString() { + var y = this.year, + m = this.month, + d = this.day; + var h = this.hour, + mm = this.minute, + s = this.second; + var hasYear = y !== null, + hasMonth = m !== null, + hasDay = d !== null; + var hasHour = h !== null, + hasMinute = mm !== null, + hasSecond = s !== null; + var datepart = (hasYear ? pad2(y) + (hasMonth || hasDay ? '-' : '') : hasMonth || hasDay ? '--' : '') + (hasMonth ? pad2(m) : '') + (hasDay ? '-' + pad2(d) : ''); + var timepart = (hasHour ? pad2(h) : '-') + (hasHour && hasMinute ? ':' : '') + (hasMinute ? pad2(mm) : '') + (!hasHour && !hasMinute ? '-' : '') + (hasMinute && hasSecond ? ':' : '') + (hasSecond ? pad2(s) : ''); + var zone; + if (this.zone === Timezone.utcTimezone) { + zone = 'Z'; + } else if (this.zone instanceof UtcOffset) { + zone = this.zone.toString(); + } else if (this.zone === Timezone.localTimezone) { + zone = ''; + } else if (this.zone instanceof Timezone) { + var offset = UtcOffset.fromSeconds(this.zone.utcOffset(this)); + zone = offset.toString(); + } else { + zone = ''; + } + switch (this.icaltype) { + case "time": + return timepart + zone; + case "date-and-or-time": + case "date-time": + return datepart + (timepart == '--' ? '' : 'T' + timepart + zone); + case "date": + return datepart; + } + return null; + } + }], [{ + key: "fromDateAndOrTimeString", + value: + /** + * Returns a new ICAL.VCardTime instance from a date and/or time string. + * + * @param {String} aValue The string to create from + * @param {String} aIcalType The type for this instance, e.g. date-and-or-time + * @return {VCardTime} The date/time instance + */ + function fromDateAndOrTimeString(aValue, aIcalType) { + function part(v, s, e) { + return v ? strictParseInt(v.slice(s, s + e)) : null; + } + var parts = aValue.split('T'); + var dt = parts[0], + tmz = parts[1]; + var splitzone = tmz ? design.vcard.value.time._splitZone(tmz) : []; + var zone = splitzone[0], + tm = splitzone[1]; + var dtlen = dt ? dt.length : 0; + var tmlen = tm ? tm.length : 0; + var hasDashDate = dt && dt[0] == '-' && dt[1] == '-'; + var hasDashTime = tm && tm[0] == '-'; + var o = { + year: hasDashDate ? null : part(dt, 0, 4), + month: hasDashDate && (dtlen == 4 || dtlen == 7) ? part(dt, 2, 2) : dtlen == 7 ? part(dt, 5, 2) : dtlen == 10 ? part(dt, 5, 2) : null, + day: dtlen == 5 ? part(dt, 3, 2) : dtlen == 7 && hasDashDate ? part(dt, 5, 2) : dtlen == 10 ? part(dt, 8, 2) : null, + hour: hasDashTime ? null : part(tm, 0, 2), + minute: hasDashTime && tmlen == 3 ? part(tm, 1, 2) : tmlen > 4 ? hasDashTime ? part(tm, 1, 2) : part(tm, 3, 2) : null, + second: tmlen == 4 ? part(tm, 2, 2) : tmlen == 6 ? part(tm, 4, 2) : tmlen == 8 ? part(tm, 6, 2) : null + }; + if (zone == 'Z') { + zone = Timezone.utcTimezone; + } else if (zone && zone[3] == ':') { + zone = UtcOffset.fromString(zone); + } else { + zone = null; + } + return new VCardTime(o, zone, aIcalType); + } + }]); + }(Time); + + /** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * + * @ignore + * @typedef {import("./types.js").weekDay} weekDay + * Imports the 'weekDay' type from the "types.js" module + */ + + /** + * An iterator for a single recurrence rule. This class usually doesn't have to be instanciated + * directly, the convenience method {@link ICAL.Recur#iterator} can be used. + * + * @memberof ICAL + */ + var RecurIterator = /*#__PURE__*/function () { + /** + * Creates a new ICAL.RecurIterator instance. The options object may contain additional members + * when resuming iteration from a previous run. + * + * @param {Object} options The iterator options + * @param {Recur} options.rule The rule to iterate. + * @param {Time} options.dtstart The start date of the event. + * @param {Boolean=} options.initialized When true, assume that options are + * from a previously constructed iterator. Initialization will not be + * repeated. + */ + function RecurIterator(options) { + _classCallCheck(this, RecurIterator); + /** + * True when iteration is finished. + * @type {Boolean} + */ + _defineProperty(this, "completed", false); + /** + * The rule that is being iterated + * @type {Recur} + */ + _defineProperty(this, "rule", null); + /** + * The start date of the event being iterated. + * @type {Time} + */ + _defineProperty(this, "dtstart", null); + /** + * The last occurrence that was returned from the + * {@link RecurIterator#next} method. + * @type {Time} + */ + _defineProperty(this, "last", null); + /** + * The sequence number from the occurrence + * @type {Number} + */ + _defineProperty(this, "occurrence_number", 0); + /** + * The indices used for the {@link ICAL.RecurIterator#by_data} object. + * @type {Object} + * @private + */ + _defineProperty(this, "by_indices", null); + /** + * If true, the iterator has already been initialized + * @type {Boolean} + * @private + */ + _defineProperty(this, "initialized", false); + /** + * The initializd by-data. + * @type {Object} + * @private + */ + _defineProperty(this, "by_data", null); + /** + * The expanded yeardays + * @type {Array} + * @private + */ + _defineProperty(this, "days", null); + /** + * The index in the {@link ICAL.RecurIterator#days} array. + * @type {Number} + * @private + */ + _defineProperty(this, "days_index", 0); + this.fromData(options); + } + return _createClass(RecurIterator, [{ + key: "fromData", + value: + /** + * Initialize the recurrence iterator from the passed data object. This + * method is usually not called directly, you can initialize the iterator + * through the constructor. + * + * @param {Object} options The iterator options + * @param {Recur} options.rule The rule to iterate. + * @param {Time} options.dtstart The start date of the event. + * @param {Boolean=} options.initialized When true, assume that options are + * from a previously constructed iterator. Initialization will not be + * repeated. + */ + function fromData(options) { + this.rule = formatClassType(options.rule, Recur); + if (!this.rule) { + throw new Error('iterator requires a (ICAL.Recur) rule'); + } + this.dtstart = formatClassType(options.dtstart, Time); + if (!this.dtstart) { + throw new Error('iterator requires a (ICAL.Time) dtstart'); + } + if (options.by_data) { + this.by_data = options.by_data; + } else { + this.by_data = clone(this.rule.parts, true); + } + if (options.occurrence_number) this.occurrence_number = options.occurrence_number; + this.days = options.days || []; + if (options.last) { + this.last = formatClassType(options.last, Time); + } + this.by_indices = options.by_indices; + if (!this.by_indices) { + this.by_indices = { + "BYSECOND": 0, + "BYMINUTE": 0, + "BYHOUR": 0, + "BYDAY": 0, + "BYMONTH": 0, + "BYWEEKNO": 0, + "BYMONTHDAY": 0 + }; + } + this.initialized = options.initialized || false; + if (!this.initialized) { + try { + this.init(); + } catch (e) { + if (e instanceof InvalidRecurrenceRuleError) { + // Init may error if there are no possible recurrence instances from + // the rule, but we don't want to bubble this error up. Instead, we + // create an empty iterator. + this.completed = true; + } else { + // Propagate other errors to consumers. + throw e; + } + } + } + } + + /** + * Initialize the iterator + * @private + */ + }, { + key: "init", + value: function init() { + var _this = this; + this.initialized = true; + this.last = this.dtstart.clone(); + var parts = this.by_data; + if ("BYDAY" in parts) { + // libical does this earlier when the rule is loaded, but we postpone to + // now so we can preserve the original order. + this.sort_byday_rules(parts.BYDAY); + } + + // The BYYEARDAY may only appear with BYDAY + if ("BYYEARDAY" in parts) { + if ("BYMONTH" in parts || "BYWEEKNO" in parts || "BYMONTHDAY" in parts) { + throw new Error("Invalid BYYEARDAY rule"); + } + } + + // BYWEEKNO and BYMONTHDAY rule parts may not both appear + if ("BYWEEKNO" in parts && "BYMONTHDAY" in parts) { + throw new Error("BYWEEKNO does not fit to BYMONTHDAY"); + } + + // For MONTHLY recurrences (FREQ=MONTHLY) neither BYYEARDAY nor + // BYWEEKNO may appear. + if (this.rule.freq == "MONTHLY" && ("BYYEARDAY" in parts || "BYWEEKNO" in parts)) { + throw new Error("For MONTHLY recurrences neither BYYEARDAY nor BYWEEKNO may appear"); + } + + // For WEEKLY recurrences (FREQ=WEEKLY) neither BYMONTHDAY nor + // BYYEARDAY may appear. + if (this.rule.freq == "WEEKLY" && ("BYYEARDAY" in parts || "BYMONTHDAY" in parts)) { + throw new Error("For WEEKLY recurrences neither BYMONTHDAY nor BYYEARDAY may appear"); + } + + // BYYEARDAY may only appear in YEARLY rules + if (this.rule.freq != "YEARLY" && "BYYEARDAY" in parts) { + throw new Error("BYYEARDAY may only appear in YEARLY rules"); + } + this.last.second = this.setup_defaults("BYSECOND", "SECONDLY", this.dtstart.second); + this.last.minute = this.setup_defaults("BYMINUTE", "MINUTELY", this.dtstart.minute); + this.last.hour = this.setup_defaults("BYHOUR", "HOURLY", this.dtstart.hour); + this.last.day = this.setup_defaults("BYMONTHDAY", "DAILY", this.dtstart.day); + this.last.month = this.setup_defaults("BYMONTH", "MONTHLY", this.dtstart.month); + if (this.rule.freq == "WEEKLY") { + if ("BYDAY" in parts) { + var _this$ruleDayOfWeek = this.ruleDayOfWeek(parts.BYDAY[0], this.rule.wkst), + _this$ruleDayOfWeek2 = _slicedToArray(_this$ruleDayOfWeek, 2), + dow = _this$ruleDayOfWeek2[1]; + var wkdy = dow - this.last.dayOfWeek(this.rule.wkst); + if (this.last.dayOfWeek(this.rule.wkst) < dow && wkdy >= 0 || wkdy < 0) { + // Initial time is after first day of BYDAY data + this.last.day += wkdy; + } + } else { + var dayName = Recur.numericDayToIcalDay(this.dtstart.dayOfWeek()); + parts.BYDAY = [dayName]; + } + } + if (this.rule.freq == "YEARLY") { + // Some yearly recurrence rules may be specific enough to not actually + // occur on a yearly basis, e.g. the 29th day of February or the fifth + // Monday of a given month. The standard isn't clear on the intended + // behavior in these cases, but `libical` at least will iterate until it + // finds a matching year. + // CAREFUL: Some rules may specify an occurrence that can never happen, + // e.g. the first Monday of April so long as it falls on the 15th + // through the 21st. Detecting these is non-trivial, so ensure that we + // stop iterating at some point. + var untilYear = this.rule.until ? this.rule.until.year : 20000; + while (this.last.year <= untilYear) { + this.expand_year_days(this.last.year); + if (this.days.length > 0) { + break; + } + this.increment_year(this.rule.interval); + } + if (this.days.length == 0) { + throw new InvalidRecurrenceRuleError(); + } + + // If there's no occurrence in this year, try the following years. This + // would only happen looking for day 366 or -366. + if (!this._nextByYearDay() && !this.next_year() && !this.next_year() && !this.next_year()) { + // This should not be possible, but just in case it is, stop. + throw new InvalidRecurrenceRuleError(); + } + } + if (this.rule.freq == "MONTHLY") { + if (this.has_by_data("BYDAY")) { + var tempLast = null; + var initLast = this.last.clone(); + var daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + + // Check every weekday in BYDAY with relative dow and pos. + var _iterator = _createForOfIteratorHelper(this.by_data.BYDAY), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var bydow = _step.value; + this.last = initLast.clone(); + var _this$ruleDayOfWeek3 = this.ruleDayOfWeek(bydow), + _this$ruleDayOfWeek4 = _slicedToArray(_this$ruleDayOfWeek3, 2), + pos = _this$ruleDayOfWeek4[0], + _dow = _this$ruleDayOfWeek4[1]; + var dayOfMonth = this.last.nthWeekDay(_dow, pos); + + // If |pos| >= 6, the byday is invalid for a monthly rule. + if (pos >= 6 || pos <= -6) { + throw new Error("Malformed values in BYDAY part"); + } + + // If a Byday with pos=+/-5 is not in the current month it + // must be searched in the next months. + if (dayOfMonth > daysInMonth || dayOfMonth <= 0) { + // Skip if we have already found a "last" in this month. + if (tempLast && tempLast.month == initLast.month) { + continue; + } + while (dayOfMonth > daysInMonth || dayOfMonth <= 0) { + this.increment_month(); + daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + dayOfMonth = this.last.nthWeekDay(_dow, pos); + } + } + this.last.day = dayOfMonth; + if (!tempLast || this.last.compare(tempLast) < 0) { + tempLast = this.last.clone(); + } + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + this.last = tempLast.clone(); + + //XXX: This feels like a hack, but we need to initialize + // the BYMONTHDAY case correctly and byDayAndMonthDay handles + // this case. It accepts a special flag which will avoid incrementing + // the initial value without the flag days that match the start time + // would be missed. + if (this.has_by_data('BYMONTHDAY')) { + this._byDayAndMonthDay(true); + } + if (this.last.day > daysInMonth || this.last.day == 0) { + throw new Error("Malformed values in BYDAY part"); + } + } else if (this.has_by_data("BYMONTHDAY")) { + // Change the day value so that normalisation won't change the month. + this.last.day = 1; + + // Get a sorted list of days in the starting month that match the rule. + var normalized = this.normalizeByMonthDayRules(this.last.year, this.last.month, this.rule.parts.BYMONTHDAY).filter(function (d) { + return d >= _this.last.day; + }); + if (normalized.length) { + // There's at least one valid day, use it. + this.last.day = normalized[0]; + this.by_data.BYMONTHDAY = normalized; + } else { + // There's no occurrence in this month, find the next valid month. + // The longest possible sequence of skipped months is February-April-June, + // so we might need to call next_month up to three times. + if (!this.next_month() && !this.next_month() && !this.next_month()) { + throw new InvalidRecurrenceRuleError(); + } + } + } + } + } + + /** + * Retrieve the next occurrence from the iterator. + * @return {Time} + */ + }, { + key: "next", + value: function next() { + var again = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var before = this.last ? this.last.clone() : null; + if (this.rule.count && this.occurrence_number >= this.rule.count || this.rule.until && this.last.compare(this.rule.until) > 0) { + this.completed = true; + } + if (this.completed) { + return null; + } + if (this.occurrence_number == 0 && this.last.compare(this.dtstart) >= 0) { + // First of all, give the instance that was initialized + this.occurrence_number++; + return this.last; + } + var valid; + var invalid_count = 0; + do { + valid = 1; + switch (this.rule.freq) { + case "SECONDLY": + this.next_second(); + break; + case "MINUTELY": + this.next_minute(); + break; + case "HOURLY": + this.next_hour(); + break; + case "DAILY": + this.next_day(); + break; + case "WEEKLY": + this.next_week(); + break; + case "MONTHLY": + valid = this.next_month(); + if (valid) { + invalid_count = 0; + } else if (++invalid_count == 336) { + // We've been through all 91 month variations and not found a recurrence. Stop. + // (12 months and 29-day February × 7 starting days.) + this.completed = true; + return null; + } + break; + case "YEARLY": + valid = this.next_year(); + if (valid) { + invalid_count = 0; + } else if (++invalid_count == 28) { + // We've been through all 14 year variations and not found a recurrence. Stop. + // (365-day and 366-day years × 7 starting days.) + this.completed = true; + return null; + } + break; + default: + return null; + } + } while (!this.check_contracting_rules() || this.last.compare(this.dtstart) < 0 || !valid); + if (this.last.compare(before) == 0) { + if (again) { + throw new Error("Same occurrence found twice, protecting you from death by recursion"); + } + this.next(true); + } + if (this.rule.until && this.last.compare(this.rule.until) > 0) { + this.completed = true; + return null; + } else { + this.occurrence_number++; + return this.last; + } + } + }, { + key: "next_second", + value: function next_second() { + return this.next_generic("BYSECOND", "SECONDLY", "second", "minute"); + } + }, { + key: "increment_second", + value: function increment_second(inc) { + return this.increment_generic(inc, "second", 60, "minute"); + } + }, { + key: "next_minute", + value: function next_minute() { + return this.next_generic("BYMINUTE", "MINUTELY", "minute", "hour", "next_second"); + } + }, { + key: "increment_minute", + value: function increment_minute(inc) { + return this.increment_generic(inc, "minute", 60, "hour"); + } + }, { + key: "next_hour", + value: function next_hour() { + return this.next_generic("BYHOUR", "HOURLY", "hour", "monthday", "next_minute"); + } + }, { + key: "increment_hour", + value: function increment_hour(inc) { + this.increment_generic(inc, "hour", 24, "monthday"); + } + }, { + key: "next_day", + value: function next_day() { + var this_freq = this.rule.freq == "DAILY"; + if (this.next_hour() == 0) { + return 0; + } + if (this_freq) { + this.increment_monthday(this.rule.interval); + } else { + this.increment_monthday(1); + } + return 0; + } + }, { + key: "next_week", + value: function next_week() { + var end_of_data = 0; + if (this.next_weekday_by_week() == 0) { + return end_of_data; + } + if (this.has_by_data("BYWEEKNO")) { + this.by_indices.BYWEEKNO++; + if (this.by_indices.BYWEEKNO == this.by_data.BYWEEKNO.length) { + this.by_indices.BYWEEKNO = 0; + end_of_data = 1; + } + + // HACK should be first month of the year + this.last.month = 1; + this.last.day = 1; + var week_no = this.by_data.BYWEEKNO[this.by_indices.BYWEEKNO]; + this.last.day += 7 * week_no; + if (end_of_data) { + this.increment_year(1); + } + } else { + // Jump to the next week + this.increment_monthday(7 * this.rule.interval); + } + return end_of_data; + } + + /** + * Normalize each by day rule for a given year/month. + * Takes into account ordering and negative rules + * + * @private + * @param {Number} year Current year. + * @param {Number} month Current month. + * @param {Array} rules Array of rules. + * + * @return {Array} sorted and normalized rules. + * Negative rules will be expanded to their + * correct positive values for easier processing. + */ + }, { + key: "normalizeByMonthDayRules", + value: function normalizeByMonthDayRules(year, month, rules) { + var daysInMonth = Time.daysInMonth(month, year); + + // XXX: This is probably bad for performance to allocate + // a new array for each month we scan, if possible + // we should try to optimize this... + var newRules = []; + var ruleIdx = 0; + var len = rules.length; + var rule; + for (; ruleIdx < len; ruleIdx++) { + rule = parseInt(rules[ruleIdx], 10); + if (isNaN(rule)) { + throw new Error('Invalid BYMONTHDAY value'); + } + + // if this rule falls outside of given + // month discard it. + if (Math.abs(rule) > daysInMonth) { + continue; + } + + // negative case + if (rule < 0) { + // we add (not subtract it is a negative number) + // one from the rule because 1 === last day of month + rule = daysInMonth + (rule + 1); + } else if (rule === 0) { + // skip zero: it is invalid. + continue; + } + + // only add unique items... + if (newRules.indexOf(rule) === -1) { + newRules.push(rule); + } + } + + // unique and sort + return newRules.sort(function (a, b) { + return a - b; + }); + } + + /** + * NOTES: + * We are given a list of dates in the month (BYMONTHDAY) (23, etc..) + * Also we are given a list of days (BYDAY) (MO, 2SU, etc..) when + * both conditions match a given date (this.last.day) iteration stops. + * + * @private + * @param {Boolean=} isInit When given true will not increment the + * current day (this.last). + */ + }, { + key: "_byDayAndMonthDay", + value: function _byDayAndMonthDay(isInit) { + var byMonthDay; // setup in initMonth + var byDay = this.by_data.BYDAY; + var date; + var dateIdx = 0; + var dateLen; // setup in initMonth + var dayLen = byDay.length; + + // we are not valid by default + var dataIsValid = 0; + var daysInMonth; + var self = this; + // we need a copy of this, because a DateTime gets normalized + // automatically if the day is out of range. At some points we + // set the last day to 0 to start counting. + var lastDay = this.last.day; + function initMonth() { + daysInMonth = Time.daysInMonth(self.last.month, self.last.year); + byMonthDay = self.normalizeByMonthDayRules(self.last.year, self.last.month, self.by_data.BYMONTHDAY); + dateLen = byMonthDay.length; + + // For the case of more than one occurrence in one month + // we have to be sure to start searching after the last + // found date or at the last BYMONTHDAY, unless we are + // initializing the iterator because in this case we have + // to consider the last found date too. + while (byMonthDay[dateIdx] <= lastDay && !(isInit && byMonthDay[dateIdx] == lastDay) && dateIdx < dateLen - 1) { + dateIdx++; + } + } + function nextMonth() { + // since the day is incremented at the start + // of the loop below, we need to start at 0 + lastDay = 0; + self.increment_month(); + dateIdx = 0; + initMonth(); + } + initMonth(); + + // should come after initMonth + if (isInit) { + lastDay -= 1; + } + + // Use a counter to avoid an infinite loop with malformed rules. + // Stop checking after 4 years so we consider also a leap year. + var monthsCounter = 48; + while (!dataIsValid && monthsCounter) { + monthsCounter--; + // increment the current date. This is really + // important otherwise we may fall into the infinite + // loop trap. The initial date takes care of the case + // where the current date is the date we are looking + // for. + date = lastDay + 1; + if (date > daysInMonth) { + nextMonth(); + continue; + } + + // find next date + var next = byMonthDay[dateIdx++]; + + // this logic is dependent on the BYMONTHDAYS + // being in order (which is done by #normalizeByMonthDayRules) + if (next >= date) { + // if the next month day is in the future jump to it. + lastDay = next; + } else { + // in this case the 'next' monthday has past + // we must move to the month. + nextMonth(); + continue; + } + + // Now we can loop through the day rules to see + // if one matches the current month date. + for (var dayIdx = 0; dayIdx < dayLen; dayIdx++) { + var parts = this.ruleDayOfWeek(byDay[dayIdx]); + var pos = parts[0]; + var dow = parts[1]; + this.last.day = lastDay; + if (this.last.isNthWeekDay(dow, pos)) { + // when we find the valid one we can mark + // the conditions as met and break the loop. + // (Because we have this condition above + // it will also break the parent loop). + dataIsValid = 1; + break; + } + } + + // It is completely possible that the combination + // cannot be matched in the current month. + // When we reach the end of possible combinations + // in the current month we iterate to the next one. + // since dateIdx is incremented right after getting + // "next", we don't need dateLen -1 here. + if (!dataIsValid && dateIdx === dateLen) { + nextMonth(); + continue; + } + } + if (monthsCounter <= 0) { + // Checked 4 years without finding a Byday that matches + // a Bymonthday. Maybe the rule is not correct. + throw new Error("Malformed values in BYDAY combined with BYMONTHDAY parts"); + } + return dataIsValid; + } + }, { + key: "next_month", + value: function next_month() { + var data_valid = 1; + if (this.next_hour() == 0) { + return data_valid; + } + if (this.has_by_data("BYDAY") && this.has_by_data("BYMONTHDAY")) { + data_valid = this._byDayAndMonthDay(); + } else if (this.has_by_data("BYDAY")) { + var daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + var setpos = 0; + var setpos_total = 0; + if (this.has_by_data("BYSETPOS")) { + var last_day = this.last.day; + for (var _day = 1; _day <= daysInMonth; _day++) { + this.last.day = _day; + if (this.is_day_in_byday(this.last)) { + setpos_total++; + if (_day <= last_day) { + setpos++; + } + } + } + this.last.day = last_day; + } + data_valid = 0; + var day; + for (day = this.last.day + 1; day <= daysInMonth; day++) { + this.last.day = day; + if (this.is_day_in_byday(this.last)) { + if (!this.has_by_data("BYSETPOS") || this.check_set_position(++setpos) || this.check_set_position(setpos - setpos_total - 1)) { + data_valid = 1; + break; + } + } + } + if (day > daysInMonth) { + this.last.day = 1; + this.increment_month(); + if (this.is_day_in_byday(this.last)) { + if (!this.has_by_data("BYSETPOS") || this.check_set_position(1)) { + data_valid = 1; + } + } else { + data_valid = 0; + } + } + } else if (this.has_by_data("BYMONTHDAY")) { + this.by_indices.BYMONTHDAY++; + if (this.by_indices.BYMONTHDAY >= this.by_data.BYMONTHDAY.length) { + this.by_indices.BYMONTHDAY = 0; + this.increment_month(); + if (this.by_indices.BYMONTHDAY >= this.by_data.BYMONTHDAY.length) { + return 0; + } + } + var _daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + var _day2 = this.by_data.BYMONTHDAY[this.by_indices.BYMONTHDAY]; + if (_day2 < 0) { + _day2 = _daysInMonth + _day2 + 1; + } + if (_day2 > _daysInMonth) { + this.last.day = 1; + data_valid = this.is_day_in_byday(this.last); + } else { + this.last.day = _day2; + } + } else { + this.increment_month(); + var _daysInMonth2 = Time.daysInMonth(this.last.month, this.last.year); + if (this.by_data.BYMONTHDAY[0] > _daysInMonth2) { + data_valid = 0; + } else { + this.last.day = this.by_data.BYMONTHDAY[0]; + } + } + return data_valid; + } + }, { + key: "next_weekday_by_week", + value: function next_weekday_by_week() { + var end_of_data = 0; + if (this.next_hour() == 0) { + return end_of_data; + } + if (!this.has_by_data("BYDAY")) { + return 1; + } + for (;;) { + var tt = new Time(); + this.by_indices.BYDAY++; + if (this.by_indices.BYDAY == Object.keys(this.by_data.BYDAY).length) { + this.by_indices.BYDAY = 0; + end_of_data = 1; + } + var coded_day = this.by_data.BYDAY[this.by_indices.BYDAY]; + var parts = this.ruleDayOfWeek(coded_day); + var dow = parts[1]; + dow -= this.rule.wkst; + if (dow < 0) { + dow += 7; + } + tt.year = this.last.year; + tt.month = this.last.month; + tt.day = this.last.day; + var startOfWeek = tt.startDoyWeek(this.rule.wkst); + if (dow + startOfWeek < 1) { + // The selected date is in the previous year + if (!end_of_data) { + continue; + } + } + var next = Time.fromDayOfYear(startOfWeek + dow, this.last.year); + + /** + * The normalization horrors below are due to + * the fact that when the year/month/day changes + * it can effect the other operations that come after. + */ + this.last.year = next.year; + this.last.month = next.month; + this.last.day = next.day; + return end_of_data; + } + } + }, { + key: "next_year", + value: function next_year() { + if (this.next_hour() == 0) { + return 0; + } + if (this.days.length == 0 || ++this.days_index == this.days.length) { + this.days_index = 0; + this.increment_year(this.rule.interval); + if (this.has_by_data("BYMONTHDAY")) { + this.by_data.BYMONTHDAY = this.normalizeByMonthDayRules(this.last.year, this.last.month, this.rule.parts.BYMONTHDAY); + } + this.expand_year_days(this.last.year); + if (this.days.length == 0) { + return 0; + } + } + return this._nextByYearDay(); + } + }, { + key: "_nextByYearDay", + value: function _nextByYearDay() { + var doy = this.days[this.days_index]; + var year = this.last.year; + if (Math.abs(doy) == 366 && !Time.isLeapYear(this.last.year)) { + return 0; + } + if (doy < 1) { + // Time.fromDayOfYear(doy, year) indexes relative to the + // start of the given year. That is different from the + // semantics of BYYEARDAY where negative indexes are an + // offset from the end of the given year. + doy += 1; + year += 1; + } + var next = Time.fromDayOfYear(doy, year); + this.last.day = next.day; + this.last.month = next.month; + return 1; + } + + /** + * @param dow (eg: '1TU', '-1MO') + * @param {weekDay=} aWeekStart The week start weekday + * @return [pos, numericDow] (eg: [1, 3]) numericDow is relative to aWeekStart + */ + }, { + key: "ruleDayOfWeek", + value: function ruleDayOfWeek(dow, aWeekStart) { + var matches = dow.match(/([+-]?[0-9])?(MO|TU|WE|TH|FR|SA|SU)/); + if (matches) { + var pos = parseInt(matches[1] || 0, 10); + dow = Recur.icalDayToNumericDay(matches[2], aWeekStart); + return [pos, dow]; + } else { + return [0, 0]; + } + } + }, { + key: "next_generic", + value: function next_generic(aRuleType, aInterval, aDateAttr, aFollowingAttr, aPreviousIncr) { + var has_by_rule = aRuleType in this.by_data; + var this_freq = this.rule.freq == aInterval; + var end_of_data = 0; + if (aPreviousIncr && this[aPreviousIncr]() == 0) { + return end_of_data; + } + if (has_by_rule) { + this.by_indices[aRuleType]++; + var dta = this.by_data[aRuleType]; + if (this.by_indices[aRuleType] == dta.length) { + this.by_indices[aRuleType] = 0; + end_of_data = 1; + } + this.last[aDateAttr] = dta[this.by_indices[aRuleType]]; + } else if (this_freq) { + this["increment_" + aDateAttr](this.rule.interval); + } + if (has_by_rule && end_of_data && this_freq) { + this["increment_" + aFollowingAttr](1); + } + return end_of_data; + } + }, { + key: "increment_monthday", + value: function increment_monthday(inc) { + for (var i = 0; i < inc; i++) { + var daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + this.last.day++; + if (this.last.day > daysInMonth) { + this.last.day -= daysInMonth; + this.increment_month(); + } + } + } + }, { + key: "increment_month", + value: function increment_month() { + this.last.day = 1; + if (this.has_by_data("BYMONTH")) { + this.by_indices.BYMONTH++; + if (this.by_indices.BYMONTH == this.by_data.BYMONTH.length) { + this.by_indices.BYMONTH = 0; + this.increment_year(1); + } + this.last.month = this.by_data.BYMONTH[this.by_indices.BYMONTH]; + } else { + if (this.rule.freq == "MONTHLY") { + this.last.month += this.rule.interval; + } else { + this.last.month++; + } + this.last.month--; + var years = trunc(this.last.month / 12); + this.last.month %= 12; + this.last.month++; + if (years != 0) { + this.increment_year(years); + } + } + if (this.has_by_data("BYMONTHDAY")) { + this.by_data.BYMONTHDAY = this.normalizeByMonthDayRules(this.last.year, this.last.month, this.rule.parts.BYMONTHDAY); + } + } + }, { + key: "increment_year", + value: function increment_year(inc) { + // Don't jump into the next month if this.last is Feb 29. + this.last.day = 1; + this.last.year += inc; + } + }, { + key: "increment_generic", + value: function increment_generic(inc, aDateAttr, aFactor, aNextIncrement) { + this.last[aDateAttr] += inc; + var nextunit = trunc(this.last[aDateAttr] / aFactor); + this.last[aDateAttr] %= aFactor; + if (nextunit != 0) { + this["increment_" + aNextIncrement](nextunit); + } + } + }, { + key: "has_by_data", + value: function has_by_data(aRuleType) { + return aRuleType in this.rule.parts; + } + }, { + key: "expand_year_days", + value: function expand_year_days(aYear) { + var t = new Time(); + this.days = []; + + // We need our own copy with a few keys set + var parts = {}; + var rules = ["BYDAY", "BYWEEKNO", "BYMONTHDAY", "BYMONTH", "BYYEARDAY"]; + for (var _i = 0, _rules = rules; _i < _rules.length; _i++) { + var part = _rules[_i]; + if (part in this.rule.parts) { + parts[part] = this.rule.parts[part]; + } + } + if ("BYMONTH" in parts && "BYWEEKNO" in parts) { + var valid = 1; + var validWeeks = {}; + t.year = aYear; + t.isDate = true; + for (var monthIdx = 0; monthIdx < this.by_data.BYMONTH.length; monthIdx++) { + var month = this.by_data.BYMONTH[monthIdx]; + t.month = month; + t.day = 1; + var first_week = t.weekNumber(this.rule.wkst); + t.day = Time.daysInMonth(month, aYear); + var last_week = t.weekNumber(this.rule.wkst); + for (monthIdx = first_week; monthIdx < last_week; monthIdx++) { + validWeeks[monthIdx] = 1; + } + } + for (var weekIdx = 0; weekIdx < this.by_data.BYWEEKNO.length && valid; weekIdx++) { + var weekno = this.by_data.BYWEEKNO[weekIdx]; + if (weekno < 52) { + valid &= validWeeks[weekIdx]; + } else { + valid = 0; + } + } + if (valid) { + delete parts.BYMONTH; + } else { + delete parts.BYWEEKNO; + } + } + var partCount = Object.keys(parts).length; + if (partCount == 0) { + var t1 = this.dtstart.clone(); + t1.year = this.last.year; + this.days.push(t1.dayOfYear()); + } else if (partCount == 1 && "BYMONTH" in parts) { + var _iterator2 = _createForOfIteratorHelper(this.by_data.BYMONTH), + _step2; + try { + for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { + var _month = _step2.value; + var t2 = this.dtstart.clone(); + t2.year = aYear; + t2.month = _month; + t2.isDate = true; + this.days.push(t2.dayOfYear()); + } + } catch (err) { + _iterator2.e(err); + } finally { + _iterator2.f(); + } + } else if (partCount == 1 && "BYMONTHDAY" in parts) { + var _iterator3 = _createForOfIteratorHelper(this.by_data.BYMONTHDAY), + _step3; + try { + for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { + var monthday = _step3.value; + var t3 = this.dtstart.clone(); + if (monthday < 0) { + var daysInMonth = Time.daysInMonth(t3.month, aYear); + monthday = monthday + daysInMonth + 1; + } + t3.day = monthday; + t3.year = aYear; + t3.isDate = true; + this.days.push(t3.dayOfYear()); + } + } catch (err) { + _iterator3.e(err); + } finally { + _iterator3.f(); + } + } else if (partCount == 2 && "BYMONTHDAY" in parts && "BYMONTH" in parts) { + var _iterator4 = _createForOfIteratorHelper(this.by_data.BYMONTH), + _step4; + try { + for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { + var _month2 = _step4.value; + var _daysInMonth3 = Time.daysInMonth(_month2, aYear); + var _iterator5 = _createForOfIteratorHelper(this.by_data.BYMONTHDAY), + _step5; + try { + for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) { + var _monthday = _step5.value; + if (_monthday < 0) { + _monthday = _monthday + _daysInMonth3 + 1; + } + t.day = _monthday; + t.month = _month2; + t.year = aYear; + t.isDate = true; + this.days.push(t.dayOfYear()); + } + } catch (err) { + _iterator5.e(err); + } finally { + _iterator5.f(); + } + } + } catch (err) { + _iterator4.e(err); + } finally { + _iterator4.f(); + } + } else if (partCount == 1 && "BYWEEKNO" in parts) ; else if (partCount == 2 && "BYWEEKNO" in parts && "BYMONTHDAY" in parts) ; else if (partCount == 1 && "BYDAY" in parts) { + this.days = this.days.concat(this.expand_by_day(aYear)); + } else if (partCount == 2 && "BYDAY" in parts && "BYMONTH" in parts) { + var _iterator6 = _createForOfIteratorHelper(this.by_data.BYMONTH), + _step6; + try { + for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) { + var _month3 = _step6.value; + var _daysInMonth4 = Time.daysInMonth(_month3, aYear); + t.year = aYear; + t.month = _month3; + t.day = 1; + t.isDate = true; + var first_dow = t.dayOfWeek(); + var doy_offset = t.dayOfYear() - 1; + t.day = _daysInMonth4; + var last_dow = t.dayOfWeek(); + if (this.has_by_data("BYSETPOS")) { + var by_month_day = []; + for (var day = 1; day <= _daysInMonth4; day++) { + t.day = day; + if (this.is_day_in_byday(t)) { + by_month_day.push(day); + } + } + for (var spIndex = 0; spIndex < by_month_day.length; spIndex++) { + if (this.check_set_position(spIndex + 1) || this.check_set_position(spIndex - by_month_day.length)) { + this.days.push(doy_offset + by_month_day[spIndex]); + } + } + } else { + var _iterator7 = _createForOfIteratorHelper(this.by_data.BYDAY), + _step7; + try { + for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) { + var coded_day = _step7.value; + var bydayParts = this.ruleDayOfWeek(coded_day); + var pos = bydayParts[0]; + var dow = bydayParts[1]; + var month_day = void 0; + var first_matching_day = (dow + 7 - first_dow) % 7 + 1; + var last_matching_day = _daysInMonth4 - (last_dow + 7 - dow) % 7; + if (pos == 0) { + for (var _day3 = first_matching_day; _day3 <= _daysInMonth4; _day3 += 7) { + this.days.push(doy_offset + _day3); + } + } else if (pos > 0) { + month_day = first_matching_day + (pos - 1) * 7; + if (month_day <= _daysInMonth4) { + this.days.push(doy_offset + month_day); + } + } else { + month_day = last_matching_day + (pos + 1) * 7; + if (month_day > 0) { + this.days.push(doy_offset + month_day); + } + } + } + } catch (err) { + _iterator7.e(err); + } finally { + _iterator7.f(); + } + } + } + // Return dates in order of occurrence (1,2,3,...) instead + // of by groups of weekdays (1,8,15,...,2,9,16,...). + } catch (err) { + _iterator6.e(err); + } finally { + _iterator6.f(); + } + this.days.sort(function (a, b) { + return a - b; + }); // Comparator function allows to sort numbers. + } else if (partCount == 2 && "BYDAY" in parts && "BYMONTHDAY" in parts) { + var expandedDays = this.expand_by_day(aYear); + var _iterator8 = _createForOfIteratorHelper(expandedDays), + _step8; + try { + for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) { + var _day4 = _step8.value; + var tt = Time.fromDayOfYear(_day4, aYear); + if (this.by_data.BYMONTHDAY.indexOf(tt.day) >= 0) { + this.days.push(_day4); + } + } + } catch (err) { + _iterator8.e(err); + } finally { + _iterator8.f(); + } + } else if (partCount == 3 && "BYDAY" in parts && "BYMONTHDAY" in parts && "BYMONTH" in parts) { + var _expandedDays = this.expand_by_day(aYear); + var _iterator9 = _createForOfIteratorHelper(_expandedDays), + _step9; + try { + for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) { + var _day5 = _step9.value; + var _tt = Time.fromDayOfYear(_day5, aYear); + if (this.by_data.BYMONTH.indexOf(_tt.month) >= 0 && this.by_data.BYMONTHDAY.indexOf(_tt.day) >= 0) { + this.days.push(_day5); + } + } + } catch (err) { + _iterator9.e(err); + } finally { + _iterator9.f(); + } + } else if (partCount == 2 && "BYDAY" in parts && "BYWEEKNO" in parts) { + var _expandedDays2 = this.expand_by_day(aYear); + var _iterator0 = _createForOfIteratorHelper(_expandedDays2), + _step0; + try { + for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) { + var _day6 = _step0.value; + var _tt2 = Time.fromDayOfYear(_day6, aYear); + var _weekno = _tt2.weekNumber(this.rule.wkst); + if (this.by_data.BYWEEKNO.indexOf(_weekno)) { + this.days.push(_day6); + } + } + } catch (err) { + _iterator0.e(err); + } finally { + _iterator0.f(); + } + } else if (partCount == 3 && "BYDAY" in parts && "BYWEEKNO" in parts && "BYMONTHDAY" in parts) ; else if (partCount == 1 && "BYYEARDAY" in parts) { + this.days = this.days.concat(this.by_data.BYYEARDAY); + } else if (partCount == 2 && "BYYEARDAY" in parts && "BYDAY" in parts) { + var _daysInYear = Time.isLeapYear(aYear) ? 366 : 365; + var _expandedDays3 = new Set(this.expand_by_day(aYear)); + var _iterator1 = _createForOfIteratorHelper(this.by_data.BYYEARDAY), + _step1; + try { + for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) { + var doy = _step1.value; + if (doy < 0) { + doy += _daysInYear + 1; + } + if (_expandedDays3.has(doy)) { + this.days.push(doy); + } + } + } catch (err) { + _iterator1.e(err); + } finally { + _iterator1.f(); + } + } else { + this.days = []; + } + var daysInYear = Time.isLeapYear(aYear) ? 366 : 365; + this.days.sort(function (a, b) { + if (a < 0) a += daysInYear + 1; + if (b < 0) b += daysInYear + 1; + return a - b; + }); + return 0; + } + }, { + key: "expand_by_day", + value: function expand_by_day(aYear) { + var days_list = []; + var tmp = this.last.clone(); + tmp.year = aYear; + tmp.month = 1; + tmp.day = 1; + tmp.isDate = true; + var start_dow = tmp.dayOfWeek(); + tmp.month = 12; + tmp.day = 31; + tmp.isDate = true; + var end_dow = tmp.dayOfWeek(); + var end_year_day = tmp.dayOfYear(); + var _iterator10 = _createForOfIteratorHelper(this.by_data.BYDAY), + _step10; + try { + for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) { + var day = _step10.value; + var parts = this.ruleDayOfWeek(day); + var pos = parts[0]; + var dow = parts[1]; + if (pos == 0) { + var tmp_start_doy = (dow + 7 - start_dow) % 7 + 1; + for (var doy = tmp_start_doy; doy <= end_year_day; doy += 7) { + days_list.push(doy); + } + } else if (pos > 0) { + var first = void 0; + if (dow >= start_dow) { + first = dow - start_dow + 1; + } else { + first = dow - start_dow + 8; + } + days_list.push(first + (pos - 1) * 7); + } else { + var last = void 0; + pos = -pos; + if (dow <= end_dow) { + last = end_year_day - end_dow + dow; + } else { + last = end_year_day - end_dow + dow - 7; + } + days_list.push(last - (pos - 1) * 7); + } + } + } catch (err) { + _iterator10.e(err); + } finally { + _iterator10.f(); + } + return days_list; + } + }, { + key: "is_day_in_byday", + value: function is_day_in_byday(tt) { + if (this.by_data.BYDAY) { + var _iterator11 = _createForOfIteratorHelper(this.by_data.BYDAY), + _step11; + try { + for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) { + var day = _step11.value; + var parts = this.ruleDayOfWeek(day); + var pos = parts[0]; + var dow = parts[1]; + var this_dow = tt.dayOfWeek(); + if (pos == 0 && dow == this_dow || tt.nthWeekDay(dow, pos) == tt.day) { + return 1; + } + } + } catch (err) { + _iterator11.e(err); + } finally { + _iterator11.f(); + } + } + return 0; + } + + /** + * Checks if given value is in BYSETPOS. + * + * @private + * @param {Numeric} aPos position to check for. + * @return {Boolean} false unless BYSETPOS rules exist + * and the given value is present in rules. + */ + }, { + key: "check_set_position", + value: function check_set_position(aPos) { + if (this.has_by_data('BYSETPOS')) { + var idx = this.by_data.BYSETPOS.indexOf(aPos); + // negative numbers are not false-y + return idx !== -1; + } + return false; + } + }, { + key: "sort_byday_rules", + value: function sort_byday_rules(aRules) { + for (var i = 0; i < aRules.length; i++) { + for (var j = 0; j < i; j++) { + var one = this.ruleDayOfWeek(aRules[j], this.rule.wkst)[1]; + var two = this.ruleDayOfWeek(aRules[i], this.rule.wkst)[1]; + if (one > two) { + var tmp = aRules[i]; + aRules[i] = aRules[j]; + aRules[j] = tmp; + } + } + } + } + }, { + key: "check_contract_restriction", + value: function check_contract_restriction(aRuleType, v) { + var indexMapValue = RecurIterator._indexMap[aRuleType]; + var ruleMapValue = RecurIterator._expandMap[this.rule.freq][indexMapValue]; + var pass = false; + if (aRuleType in this.by_data && ruleMapValue == RecurIterator.CONTRACT) { + var ruleType = this.by_data[aRuleType]; + var _iterator12 = _createForOfIteratorHelper(ruleType), + _step12; + try { + for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) { + var bydata = _step12.value; + if (bydata == v) { + pass = true; + break; + } + } + } catch (err) { + _iterator12.e(err); + } finally { + _iterator12.f(); + } + } else { + // Not a contracting byrule or has no data, test passes + pass = true; + } + return pass; + } + }, { + key: "check_contracting_rules", + value: function check_contracting_rules() { + var dow = this.last.dayOfWeek(); + var weekNo = this.last.weekNumber(this.rule.wkst); + var doy = this.last.dayOfYear(); + return this.check_contract_restriction("BYSECOND", this.last.second) && this.check_contract_restriction("BYMINUTE", this.last.minute) && this.check_contract_restriction("BYHOUR", this.last.hour) && this.check_contract_restriction("BYDAY", Recur.numericDayToIcalDay(dow)) && this.check_contract_restriction("BYWEEKNO", weekNo) && this.check_contract_restriction("BYMONTHDAY", this.last.day) && this.check_contract_restriction("BYMONTH", this.last.month) && this.check_contract_restriction("BYYEARDAY", doy); + } + }, { + key: "setup_defaults", + value: function setup_defaults(aRuleType, req, deftime) { + var indexMapValue = RecurIterator._indexMap[aRuleType]; + var ruleMapValue = RecurIterator._expandMap[this.rule.freq][indexMapValue]; + if (ruleMapValue != RecurIterator.CONTRACT) { + if (!(aRuleType in this.by_data)) { + this.by_data[aRuleType] = [deftime]; + } + if (this.rule.freq != req) { + return this.by_data[aRuleType][0]; + } + } + return deftime; + } + + /** + * Convert iterator into a serialize-able object. Will preserve current + * iteration sequence to ensure the seamless continuation of the recurrence + * rule. + * @return {Object} + */ + }, { + key: "toJSON", + value: function toJSON() { + var result = Object.create(null); + result.initialized = this.initialized; + result.rule = this.rule.toJSON(); + result.dtstart = this.dtstart.toJSON(); + result.by_data = this.by_data; + result.days = this.days; + result.last = this.last.toJSON(); + result.by_indices = this.by_indices; + result.occurrence_number = this.occurrence_number; + return result; + } + }]); + }(); + /** + * An error indicating that a recurrence rule is invalid and produces no + * occurrences. + * + * @extends {Error} + * @class + */ + _defineProperty(RecurIterator, "_indexMap", { + "BYSECOND": 0, + "BYMINUTE": 1, + "BYHOUR": 2, + "BYDAY": 3, + "BYMONTHDAY": 4, + "BYYEARDAY": 5, + "BYWEEKNO": 6, + "BYMONTH": 7, + "BYSETPOS": 8 + }); + _defineProperty(RecurIterator, "_expandMap", { + "SECONDLY": [1, 1, 1, 1, 1, 1, 1, 1], + "MINUTELY": [2, 1, 1, 1, 1, 1, 1, 1], + "HOURLY": [2, 2, 1, 1, 1, 1, 1, 1], + "DAILY": [2, 2, 2, 1, 1, 1, 1, 1], + "WEEKLY": [2, 2, 2, 2, 3, 3, 1, 1], + "MONTHLY": [2, 2, 2, 2, 2, 3, 3, 1], + "YEARLY": [2, 2, 2, 2, 2, 2, 2, 2] + }); + _defineProperty(RecurIterator, "UNKNOWN", 0); + _defineProperty(RecurIterator, "CONTRACT", 1); + _defineProperty(RecurIterator, "EXPAND", 2); + _defineProperty(RecurIterator, "ILLEGAL", 3); + var InvalidRecurrenceRuleError = /*#__PURE__*/function (_Error) { + function InvalidRecurrenceRuleError() { + _classCallCheck(this, InvalidRecurrenceRuleError); + return _callSuper(this, InvalidRecurrenceRuleError, ["Recurrence rule has no valid occurrences"]); + } + _inherits(InvalidRecurrenceRuleError, _Error); + return _createClass(InvalidRecurrenceRuleError); + }(/*#__PURE__*/_wrapNativeSuper(Error)); + + /** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * + * @ignore + * @typedef {import("./types.js").weekDay} weekDay + * Imports the 'weekDay' type from the "types.js" module + * @typedef {import("./types.js").frequencyValues} frequencyValues + * Imports the 'frequencyValues' type from the "types.js" module + */ + + var VALID_DAY_NAMES = /^(SU|MO|TU|WE|TH|FR|SA)$/; + var VALID_BYDAY_PART = /^([+-])?(5[0-3]|[1-4][0-9]|[1-9])?(SU|MO|TU|WE|TH|FR|SA)$/; + var DOW_MAP = { + SU: Time.SUNDAY, + MO: Time.MONDAY, + TU: Time.TUESDAY, + WE: Time.WEDNESDAY, + TH: Time.THURSDAY, + FR: Time.FRIDAY, + SA: Time.SATURDAY + }; + var REVERSE_DOW_MAP = Object.fromEntries(Object.entries(DOW_MAP).map(function (entry) { + return entry.reverse(); + })); + var ALLOWED_FREQ = ['SECONDLY', 'MINUTELY', 'HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY']; + + /** + * This class represents the "recur" value type, used for example by RRULE. It provides methods to + * calculate occurrences among others. + * + * @memberof ICAL + */ + var Recur = /*#__PURE__*/function () { + /** + * Create a new instance of the Recur class. + * + * @param {Object} data An object with members of the recurrence + * @param {frequencyValues=} data.freq The frequency value + * @param {Number=} data.interval The INTERVAL value + * @param {weekDay=} data.wkst The week start value + * @param {Time=} data.until The end of the recurrence set + * @param {Number=} data.count The number of occurrences + * @param {Array.=} data.bysecond The seconds for the BYSECOND part + * @param {Array.=} data.byminute The minutes for the BYMINUTE part + * @param {Array.=} data.byhour The hours for the BYHOUR part + * @param {Array.=} data.byday The BYDAY values + * @param {Array.=} data.bymonthday The days for the BYMONTHDAY part + * @param {Array.=} data.byyearday The days for the BYYEARDAY part + * @param {Array.=} data.byweekno The weeks for the BYWEEKNO part + * @param {Array.=} data.bymonth The month for the BYMONTH part + * @param {Array.=} data.bysetpos The positionals for the BYSETPOS part + */ + function Recur(data) { + _classCallCheck(this, Recur); + /** + * An object holding the BY-parts of the recurrence rule + * @memberof ICAL.Recur + * @typedef {Object} byParts + * @property {Array.=} BYSECOND The seconds for the BYSECOND part + * @property {Array.=} BYMINUTE The minutes for the BYMINUTE part + * @property {Array.=} BYHOUR The hours for the BYHOUR part + * @property {Array.=} BYDAY The BYDAY values + * @property {Array.=} BYMONTHDAY The days for the BYMONTHDAY part + * @property {Array.=} BYYEARDAY The days for the BYYEARDAY part + * @property {Array.=} BYWEEKNO The weeks for the BYWEEKNO part + * @property {Array.=} BYMONTH The month for the BYMONTH part + * @property {Array.=} BYSETPOS The positionals for the BYSETPOS part + */ + /** + * An object holding the BY-parts of the recurrence rule + * @type {byParts} + */ + _defineProperty(this, "parts", null); + /** + * The interval value for the recurrence rule. + * @type {Number} + */ + _defineProperty(this, "interval", 1); + /** + * The week start day + * + * @type {weekDay} + * @default ICAL.Time.MONDAY + */ + _defineProperty(this, "wkst", Time.MONDAY); + /** + * The end of the recurrence + * @type {?Time} + */ + _defineProperty(this, "until", null); + /** + * The maximum number of occurrences + * @type {?Number} + */ + _defineProperty(this, "count", null); + /** + * The frequency value. + * @type {frequencyValues} + */ + _defineProperty(this, "freq", null); + /** + * The class identifier. + * @constant + * @type {String} + * @default "icalrecur" + */ + _defineProperty(this, "icalclass", "icalrecur"); + /** + * The type name, to be used in the jCal object. + * @constant + * @type {String} + * @default "recur" + */ + _defineProperty(this, "icaltype", "recur"); + this.wrappedJSObject = this; + this.parts = {}; + if (data && _typeof(data) === 'object') { + this.fromData(data); + } + } + return _createClass(Recur, [{ + key: "iterator", + value: + /** + * Create a new iterator for this recurrence rule. The passed start date + * must be the start date of the event, not the start of the range to + * search in. + * + * @example + * let recur = comp.getFirstPropertyValue('rrule'); + * let dtstart = comp.getFirstPropertyValue('dtstart'); + * let iter = recur.iterator(dtstart); + * for (let next = iter.next(); next; next = iter.next()) { + * if (next.compare(rangeStart) < 0) { + * continue; + * } + * console.log(next.toString()); + * } + * + * @param {Time} aStart The item's start date + * @return {RecurIterator} The recurrence iterator + */ + function iterator(aStart) { + return new RecurIterator({ + rule: this, + dtstart: aStart + }); + } + + /** + * Returns a clone of the recurrence object. + * + * @return {Recur} The cloned object + */ + }, { + key: "clone", + value: function clone() { + return new Recur(this.toJSON()); + } + + /** + * Checks if the current rule is finite, i.e. has a count or until part. + * + * @return {Boolean} True, if the rule is finite + */ + }, { + key: "isFinite", + value: function isFinite() { + return !!(this.count || this.until); + } + + /** + * Checks if the current rule has a count part, and not limited by an until + * part. + * + * @return {Boolean} True, if the rule is by count + */ + }, { + key: "isByCount", + value: function isByCount() { + return !!(this.count && !this.until); + } + + /** + * Adds a component (part) to the recurrence rule. This is not a component + * in the sense of {@link ICAL.Component}, but a part of the recurrence + * rule, i.e. BYMONTH. + * + * @param {String} aType The name of the component part + * @param {Array|String} aValue The component value + */ + }, { + key: "addComponent", + value: function addComponent(aType, aValue) { + var ucname = aType.toUpperCase(); + if (ucname in this.parts) { + this.parts[ucname].push(aValue); + } else { + this.parts[ucname] = [aValue]; + } + } + + /** + * Sets the component value for the given by-part. + * + * @param {String} aType The component part name + * @param {Array} aValues The component values + */ + }, { + key: "setComponent", + value: function setComponent(aType, aValues) { + this.parts[aType.toUpperCase()] = aValues.slice(); + } + + /** + * Gets (a copy) of the requested component value. + * + * @param {String} aType The component part name + * @return {Array} The component part value + */ + }, { + key: "getComponent", + value: function getComponent(aType) { + var ucname = aType.toUpperCase(); + return ucname in this.parts ? this.parts[ucname].slice() : []; + } + + /** + * Retrieves the next occurrence after the given recurrence id. See the + * guide on {@tutorial terminology} for more details. + * + * NOTE: Currently, this method iterates all occurrences from the start + * date. It should not be called in a loop for performance reasons. If you + * would like to get more than one occurrence, you can iterate the + * occurrences manually, see the example on the + * {@link ICAL.Recur#iterator iterator} method. + * + * @param {Time} aStartTime The start of the event series + * @param {Time} aRecurrenceId The date of the last occurrence + * @return {Time} The next occurrence after + */ + }, { + key: "getNextOccurrence", + value: function getNextOccurrence(aStartTime, aRecurrenceId) { + var iter = this.iterator(aStartTime); + var next; + do { + next = iter.next(); + } while (next && next.compare(aRecurrenceId) <= 0); + if (next && aRecurrenceId.zone) { + next.zone = aRecurrenceId.zone; + } + return next; + } + + /** + * Sets up the current instance using members from the passed data object. + * + * @param {Object} data An object with members of the recurrence + * @param {frequencyValues=} data.freq The frequency value + * @param {Number=} data.interval The INTERVAL value + * @param {weekDay=} data.wkst The week start value + * @param {Time=} data.until The end of the recurrence set + * @param {Number=} data.count The number of occurrences + * @param {Array.=} data.bysecond The seconds for the BYSECOND part + * @param {Array.=} data.byminute The minutes for the BYMINUTE part + * @param {Array.=} data.byhour The hours for the BYHOUR part + * @param {Array.=} data.byday The BYDAY values + * @param {Array.=} data.bymonthday The days for the BYMONTHDAY part + * @param {Array.=} data.byyearday The days for the BYYEARDAY part + * @param {Array.=} data.byweekno The weeks for the BYWEEKNO part + * @param {Array.=} data.bymonth The month for the BYMONTH part + * @param {Array.=} data.bysetpos The positionals for the BYSETPOS part + */ + }, { + key: "fromData", + value: function fromData(data) { + for (var key in data) { + var uckey = key.toUpperCase(); + if (uckey in partDesign) { + if (Array.isArray(data[key])) { + this.parts[uckey] = data[key]; + } else { + this.parts[uckey] = [data[key]]; + } + } else { + this[key] = data[key]; + } + } + if (this.interval && typeof this.interval != "number") { + optionDesign.INTERVAL(this.interval, this); + } + if (this.wkst && typeof this.wkst != "number") { + this.wkst = Recur.icalDayToNumericDay(this.wkst); + } + if (this.until && !(this.until instanceof Time)) { + this.until = Time.fromString(this.until); + } + } + + /** + * The jCal representation of this recurrence type. + * @return {Object} + */ + }, { + key: "toJSON", + value: function toJSON() { + var res = Object.create(null); + res.freq = this.freq; + if (this.count) { + res.count = this.count; + } + if (this.interval > 1) { + res.interval = this.interval; + } + for (var _i = 0, _Object$entries = Object.entries(this.parts); _i < _Object$entries.length; _i++) { + var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), + k = _Object$entries$_i[0], + kparts = _Object$entries$_i[1]; + if (Array.isArray(kparts) && kparts.length == 1) { + res[k.toLowerCase()] = kparts[0]; + } else { + res[k.toLowerCase()] = clone(kparts); + } + } + if (this.until) { + res.until = this.until.toString(); + } + if ('wkst' in this && this.wkst !== Time.DEFAULT_WEEK_START) { + res.wkst = Recur.numericDayToIcalDay(this.wkst); + } + return res; + } + + /** + * The string representation of this recurrence rule. + * @return {String} + */ + }, { + key: "toString", + value: function toString() { + // TODO retain order + var str = "FREQ=" + this.freq; + if (this.count) { + str += ";COUNT=" + this.count; + } + if (this.interval > 1) { + str += ";INTERVAL=" + this.interval; + } + for (var _i2 = 0, _Object$entries2 = Object.entries(this.parts); _i2 < _Object$entries2.length; _i2++) { + var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2), + k = _Object$entries2$_i[0], + v = _Object$entries2$_i[1]; + str += ";" + k + "=" + v; + } + if (this.until) { + str += ';UNTIL=' + this.until.toICALString(); + } + if ('wkst' in this && this.wkst !== Time.DEFAULT_WEEK_START) { + str += ';WKST=' + Recur.numericDayToIcalDay(this.wkst); + } + return str; + } + }], [{ + key: "fromString", + value: + /** + * Creates a new {@link ICAL.Recur} instance from the passed string. + * + * @param {String} string The string to parse + * @return {Recur} The created recurrence instance + */ + function fromString(string) { + var data = this._stringToData(string, false); + return new Recur(data); + } + + /** + * Creates a new {@link ICAL.Recur} instance using members from the passed + * data object. + * + * @param {Object} aData An object with members of the recurrence + * @param {frequencyValues=} aData.freq The frequency value + * @param {Number=} aData.interval The INTERVAL value + * @param {weekDay=} aData.wkst The week start value + * @param {Time=} aData.until The end of the recurrence set + * @param {Number=} aData.count The number of occurrences + * @param {Array.=} aData.bysecond The seconds for the BYSECOND part + * @param {Array.=} aData.byminute The minutes for the BYMINUTE part + * @param {Array.=} aData.byhour The hours for the BYHOUR part + * @param {Array.=} aData.byday The BYDAY values + * @param {Array.=} aData.bymonthday The days for the BYMONTHDAY part + * @param {Array.=} aData.byyearday The days for the BYYEARDAY part + * @param {Array.=} aData.byweekno The weeks for the BYWEEKNO part + * @param {Array.=} aData.bymonth The month for the BYMONTH part + * @param {Array.=} aData.bysetpos The positionals for the BYSETPOS part + */ + }, { + key: "fromData", + value: function fromData(aData) { + return new Recur(aData); + } + + /** + * Converts a recurrence string to a data object, suitable for the fromData + * method. + * + * @private + * @param {String} string The string to parse + * @param {Boolean} fmtIcal If true, the string is considered to be an + * iCalendar string + * @return {Recur} The recurrence instance + */ + }, { + key: "_stringToData", + value: function _stringToData(string, fmtIcal) { + var dict = Object.create(null); + + // split is slower in FF but fast enough. + // v8 however this is faster then manual split? + var values = string.split(';'); + var len = values.length; + for (var i = 0; i < len; i++) { + var parts = values[i].split('='); + var ucname = parts[0].toUpperCase(); + var lcname = parts[0].toLowerCase(); + var name = fmtIcal ? lcname : ucname; + var value = parts[1]; + if (ucname in partDesign) { + var partArr = value.split(','); + var partSet = new Set(); + var _iterator = _createForOfIteratorHelper(partArr), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var part = _step.value; + partSet.add(partDesign[ucname](part)); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + partArr = _toConsumableArray(partSet); + dict[name] = partArr.length == 1 ? partArr[0] : partArr; + } else if (ucname in optionDesign) { + optionDesign[ucname](value, dict, fmtIcal); + } else { + // Don't swallow unknown values. Just set them as they are. + dict[lcname] = value; + } + } + return dict; + } + + /** + * Convert an ical representation of a day (SU, MO, etc..) + * into a numeric value of that day. + * + * @param {String} string The iCalendar day name + * @param {weekDay=} aWeekStart + * The week start weekday, defaults to SUNDAY + * @return {Number} Numeric value of given day + */ + }, { + key: "icalDayToNumericDay", + value: function icalDayToNumericDay(string, aWeekStart) { + //XXX: this is here so we can deal + // with possibly invalid string values. + var firstDow = aWeekStart || Time.SUNDAY; + return (DOW_MAP[string] - firstDow + 7) % 7 + 1; + } + + /** + * Convert a numeric day value into its ical representation (SU, MO, etc..) + * + * @param {Number} num Numeric value of given day + * @param {weekDay=} aWeekStart + * The week start weekday, defaults to SUNDAY + * @return {String} The ICAL day value, e.g SU,MO,... + */ + }, { + key: "numericDayToIcalDay", + value: function numericDayToIcalDay(num, aWeekStart) { + //XXX: this is here so we can deal with possibly invalid number values. + // Also, this allows consistent mapping between day numbers and day + // names for external users. + var firstDow = aWeekStart || Time.SUNDAY; + var dow = num + firstDow - Time.SUNDAY; + if (dow > 7) { + dow -= 7; + } + return REVERSE_DOW_MAP[dow]; + } + }]); + }(); + function parseNumericValue(type, min, max, value) { + var result = value; + if (value[0] === '+') { + result = value.slice(1); + } + result = strictParseInt(result); + if (min !== undefined && value < min) { + throw new Error(type + ': invalid value "' + value + '" must be > ' + min); + } + if (max !== undefined && value > max) { + throw new Error(type + ': invalid value "' + value + '" must be < ' + min); + } + return result; + } + var optionDesign = { + FREQ: function FREQ(value, dict, fmtIcal) { + // yes this is actually equal or faster then regex. + // upside here is we can enumerate the valid values. + if (ALLOWED_FREQ.indexOf(value) !== -1) { + dict.freq = value; + } else { + throw new Error('invalid frequency "' + value + '" expected: "' + ALLOWED_FREQ.join(', ') + '"'); + } + }, + COUNT: function COUNT(value, dict, fmtIcal) { + dict.count = strictParseInt(value); + }, + INTERVAL: function INTERVAL(value, dict, fmtIcal) { + dict.interval = strictParseInt(value); + if (dict.interval < 1) { + // 0 or negative values are not allowed, some engines seem to generate + // it though. Assume 1 instead. + dict.interval = 1; + } + }, + UNTIL: function UNTIL(value, dict, fmtIcal) { + if (value.length > 10) { + dict.until = design.icalendar.value['date-time'].fromICAL(value); + } else { + dict.until = design.icalendar.value.date.fromICAL(value); + } + if (!fmtIcal) { + dict.until = Time.fromString(dict.until); + } + }, + WKST: function WKST(value, dict, fmtIcal) { + if (VALID_DAY_NAMES.test(value)) { + dict.wkst = Recur.icalDayToNumericDay(value); + } else { + throw new Error('invalid WKST value "' + value + '"'); + } + } + }; + var partDesign = { + BYSECOND: parseNumericValue.bind(undefined, 'BYSECOND', 0, 60), + BYMINUTE: parseNumericValue.bind(undefined, 'BYMINUTE', 0, 59), + BYHOUR: parseNumericValue.bind(undefined, 'BYHOUR', 0, 23), + BYDAY: function BYDAY(value) { + if (VALID_BYDAY_PART.test(value)) { + return value; + } else { + throw new Error('invalid BYDAY value "' + value + '"'); + } + }, + BYMONTHDAY: parseNumericValue.bind(undefined, 'BYMONTHDAY', -31, 31), + BYYEARDAY: parseNumericValue.bind(undefined, 'BYYEARDAY', -366, 366), + BYWEEKNO: parseNumericValue.bind(undefined, 'BYWEEKNO', -53, 53), + BYMONTH: parseNumericValue.bind(undefined, 'BYMONTH', 1, 12), + BYSETPOS: parseNumericValue.bind(undefined, 'BYSETPOS', -366, 366) + }; + + /** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * @ignore + * @typedef {import("./types.js").designSet} designSet + * Imports the 'designSet' type from the "types.js" module + */ + + /** @module ICAL.design */ + + var FROM_ICAL_NEWLINE = /\\\\|\\;|\\,|\\[Nn]/g; + var TO_ICAL_NEWLINE = /\\|;|,|\n/g; + var FROM_VCARD_NEWLINE = /\\\\|\\,|\\[Nn]/g; + var TO_VCARD_NEWLINE = /\\|,|\n/g; + function createTextType(fromNewline, toNewline) { + var result = { + matches: /.*/, + fromICAL: function fromICAL(aValue, structuredEscape) { + return replaceNewline(aValue, fromNewline, structuredEscape); + }, + toICAL: function toICAL(aValue, structuredEscape) { + var regEx = toNewline; + if (structuredEscape) regEx = new RegExp(regEx.source + '|' + structuredEscape, regEx.flags); + return aValue.replace(regEx, function (str) { + switch (str) { + case "\\": + return "\\\\"; + case ";": + return "\\;"; + case ",": + return "\\,"; + case "\n": + return "\\n"; + /* c8 ignore next 2 */ + default: + return str; + } + }); + } + }; + return result; + } + + // default types used multiple times + var DEFAULT_TYPE_TEXT = { + defaultType: "text" + }; + var DEFAULT_TYPE_TEXT_MULTI = { + defaultType: "text", + multiValue: "," + }; + var DEFAULT_TYPE_TEXT_STRUCTURED = { + defaultType: "text", + structuredValue: ";" + }; + var DEFAULT_TYPE_INTEGER = { + defaultType: "integer" + }; + var DEFAULT_TYPE_DATETIME_DATE = { + defaultType: "date-time", + allowedTypes: ["date-time", "date"] + }; + var DEFAULT_TYPE_DATETIME = { + defaultType: "date-time" + }; + var DEFAULT_TYPE_URI = { + defaultType: "uri" + }; + var DEFAULT_TYPE_UTCOFFSET = { + defaultType: "utc-offset" + }; + var DEFAULT_TYPE_RECUR = { + defaultType: "recur" + }; + var DEFAULT_TYPE_DATE_ANDOR_TIME = { + defaultType: "date-and-or-time", + allowedTypes: ["date-time", "date", "text"] + }; + function replaceNewlineReplace(string) { + switch (string) { + case "\\\\": + return "\\"; + case "\\;": + return ";"; + case "\\,": + return ","; + case "\\n": + case "\\N": + return "\n"; + /* c8 ignore next 2 */ + default: + return string; + } + } + function replaceNewline(value, newline, structuredEscape) { + // avoid regex when possible. + if (value.indexOf('\\') === -1) { + return value; + } + if (structuredEscape) newline = new RegExp(newline.source + '|\\\\' + structuredEscape, newline.flags); + return value.replace(newline, replaceNewlineReplace); + } + var commonProperties = { + "categories": DEFAULT_TYPE_TEXT_MULTI, + "url": DEFAULT_TYPE_URI, + "version": DEFAULT_TYPE_TEXT, + "uid": DEFAULT_TYPE_TEXT + }; + var commonValues = { + "boolean": { + values: ["TRUE", "FALSE"], + fromICAL: function fromICAL(aValue) { + switch (aValue) { + case 'TRUE': + return true; + case 'FALSE': + return false; + default: + //TODO: parser warning + return false; + } + }, + toICAL: function toICAL(aValue) { + if (aValue) { + return 'TRUE'; + } + return 'FALSE'; + } + }, + "float": { + matches: /^[+-]?\d+\.\d+$/, + fromICAL: function fromICAL(aValue) { + var parsed = parseFloat(aValue); + if (isStrictlyNaN(parsed)) { + // TODO: parser warning + return 0.0; + } + return parsed; + }, + toICAL: function toICAL(aValue) { + return String(aValue); + } + }, + integer: { + fromICAL: function fromICAL(aValue) { + var parsed = parseInt(aValue); + if (isStrictlyNaN(parsed)) { + return 0; + } + return parsed; + }, + toICAL: function toICAL(aValue) { + return String(aValue); + } + }, + "utc-offset": { + toICAL: function toICAL(aValue) { + if (aValue.length < 7) { + // no seconds + // -0500 + return aValue.slice(0, 3) + aValue.slice(4, 6); + } else { + // seconds + // -050000 + return aValue.slice(0, 3) + aValue.slice(4, 6) + aValue.slice(7, 9); + } + }, + fromICAL: function fromICAL(aValue) { + if (aValue.length < 6) { + // no seconds + // -05:00 + return aValue.slice(0, 3) + ':' + aValue.slice(3, 5); + } else { + // seconds + // -05:00:00 + return aValue.slice(0, 3) + ':' + aValue.slice(3, 5) + ':' + aValue.slice(5, 7); + } + }, + decorate: function decorate(aValue) { + return UtcOffset.fromString(aValue); + }, + undecorate: function undecorate(aValue) { + return aValue.toString(); + } + } + }; + var icalParams = { + // Although the syntax is DQUOTE uri DQUOTE, I don't think we should + // enforce anything aside from it being a valid content line. + // + // At least some params require - if multi values are used - DQUOTEs + // for each of its values - e.g. delegated-from="uri1","uri2" + // To indicate this, I introduced the new k/v pair + // multiValueSeparateDQuote: true + // + // "ALTREP": { ... }, + + // CN just wants a param-value + // "CN": { ... } + + "cutype": { + values: ["INDIVIDUAL", "GROUP", "RESOURCE", "ROOM", "UNKNOWN"], + allowXName: true, + allowIanaToken: true + }, + "delegated-from": { + valueType: "cal-address", + multiValue: ",", + multiValueSeparateDQuote: true + }, + "delegated-to": { + valueType: "cal-address", + multiValue: ",", + multiValueSeparateDQuote: true + }, + // "DIR": { ... }, // See ALTREP + "encoding": { + values: ["8BIT", "BASE64"] + }, + // "FMTTYPE": { ... }, // See ALTREP + "fbtype": { + values: ["FREE", "BUSY", "BUSY-UNAVAILABLE", "BUSY-TENTATIVE"], + allowXName: true, + allowIanaToken: true + }, + // "LANGUAGE": { ... }, // See ALTREP + "member": { + valueType: "cal-address", + multiValue: ",", + multiValueSeparateDQuote: true + }, + "partstat": { + // TODO These values are actually different per-component + values: ["NEEDS-ACTION", "ACCEPTED", "DECLINED", "TENTATIVE", "DELEGATED", "COMPLETED", "IN-PROCESS"], + allowXName: true, + allowIanaToken: true + }, + "range": { + values: ["THISANDFUTURE"] + }, + "related": { + values: ["START", "END"] + }, + "reltype": { + values: ["PARENT", "CHILD", "SIBLING"], + allowXName: true, + allowIanaToken: true + }, + "role": { + values: ["REQ-PARTICIPANT", "CHAIR", "OPT-PARTICIPANT", "NON-PARTICIPANT"], + allowXName: true, + allowIanaToken: true + }, + "rsvp": { + values: ["TRUE", "FALSE"] + }, + "sent-by": { + valueType: "cal-address" + }, + "tzid": { + matches: /^\// + }, + "value": { + // since the value here is a 'type' lowercase is used. + values: ["binary", "boolean", "cal-address", "date", "date-time", "duration", "float", "integer", "period", "recur", "text", "time", "uri", "utc-offset"], + allowXName: true, + allowIanaToken: true + } + }; + + // When adding a value here, be sure to add it to the parameter types! + var icalValues = extend(commonValues, { + text: createTextType(FROM_ICAL_NEWLINE, TO_ICAL_NEWLINE), + uri: { + // TODO + /* ... */ + }, + "binary": { + decorate: function decorate(aString) { + return Binary.fromString(aString); + }, + undecorate: function undecorate(aBinary) { + return aBinary.toString(); + } + }, + "cal-address": { + // needs to be an uri + }, + "date": { + decorate: function decorate(aValue, aProp) { + if (design.strict) { + return Time.fromDateString(aValue, aProp); + } else { + return Time.fromString(aValue, aProp); + } + }, + /** + * undecorates a time object. + */ + undecorate: function undecorate(aValue) { + return aValue.toString(); + }, + fromICAL: function fromICAL(aValue) { + // from: 20120901 + // to: 2012-09-01 + if (!design.strict && aValue.length >= 15) { + // This is probably a date-time, e.g. 20120901T130000Z + return icalValues["date-time"].fromICAL(aValue); + } else { + return aValue.slice(0, 4) + '-' + aValue.slice(4, 6) + '-' + aValue.slice(6, 8); + } + }, + toICAL: function toICAL(aValue) { + // from: 2012-09-01 + // to: 20120901 + var len = aValue.length; + if (len == 10) { + return aValue.slice(0, 4) + aValue.slice(5, 7) + aValue.slice(8, 10); + } else if (len >= 19) { + return icalValues["date-time"].toICAL(aValue); + } else { + //TODO: serialize warning? + return aValue; + } + } + }, + "date-time": { + fromICAL: function fromICAL(aValue) { + // from: 20120901T130000 + // to: 2012-09-01T13:00:00 + if (!design.strict && aValue.length == 8) { + // This is probably a date, e.g. 20120901 + return icalValues.date.fromICAL(aValue); + } else { + var result = aValue.slice(0, 4) + '-' + aValue.slice(4, 6) + '-' + aValue.slice(6, 8) + 'T' + aValue.slice(9, 11) + ':' + aValue.slice(11, 13) + ':' + aValue.slice(13, 15); + if (aValue[15] && aValue[15] === 'Z') { + result += 'Z'; + } + return result; + } + }, + toICAL: function toICAL(aValue) { + // from: 2012-09-01T13:00:00 + // to: 20120901T130000 + var len = aValue.length; + if (len == 10 && !design.strict) { + return icalValues.date.toICAL(aValue); + } else if (len >= 19) { + var result = aValue.slice(0, 4) + aValue.slice(5, 7) + + // grab the (DDTHH) segment + aValue.slice(8, 13) + + // MM + aValue.slice(14, 16) + + // SS + aValue.slice(17, 19); + if (aValue[19] && aValue[19] === 'Z') { + result += 'Z'; + } + return result; + } else { + // TODO: error + return aValue; + } + }, + decorate: function decorate(aValue, aProp) { + if (design.strict) { + return Time.fromDateTimeString(aValue, aProp); + } else { + return Time.fromString(aValue, aProp); + } + }, + undecorate: function undecorate(aValue) { + return aValue.toString(); + } + }, + duration: { + decorate: function decorate(aValue) { + return Duration.fromString(aValue); + }, + undecorate: function undecorate(aValue) { + return aValue.toString(); + } + }, + period: { + fromICAL: function fromICAL(string) { + var parts = string.split('/'); + parts[0] = icalValues['date-time'].fromICAL(parts[0]); + if (!Duration.isValueString(parts[1])) { + parts[1] = icalValues['date-time'].fromICAL(parts[1]); + } + return parts; + }, + toICAL: function toICAL(parts) { + parts = parts.slice(); + if (!design.strict && parts[0].length == 10) { + parts[0] = icalValues.date.toICAL(parts[0]); + } else { + parts[0] = icalValues['date-time'].toICAL(parts[0]); + } + if (!Duration.isValueString(parts[1])) { + if (!design.strict && parts[1].length == 10) { + parts[1] = icalValues.date.toICAL(parts[1]); + } else { + parts[1] = icalValues['date-time'].toICAL(parts[1]); + } + } + return parts.join("/"); + }, + decorate: function decorate(aValue, aProp) { + return Period.fromJSON(aValue, aProp, !design.strict); + }, + undecorate: function undecorate(aValue) { + return aValue.toJSON(); + } + }, + recur: { + fromICAL: function fromICAL(string) { + return Recur._stringToData(string, true); + }, + toICAL: function toICAL(data) { + var str = ""; + for (var _i = 0, _Object$entries = Object.entries(data); _i < _Object$entries.length; _i++) { + var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), + k = _Object$entries$_i[0], + val = _Object$entries$_i[1]; + if (k == "until") { + if (val.length > 10) { + val = icalValues['date-time'].toICAL(val); + } else { + val = icalValues.date.toICAL(val); + } + } else if (k == "wkst") { + if (typeof val === 'number') { + val = Recur.numericDayToIcalDay(val); + } + } else if (Array.isArray(val)) { + val = val.join(","); + } + str += k.toUpperCase() + "=" + val + ";"; + } + return str.slice(0, Math.max(0, str.length - 1)); + }, + decorate: function decorate(aValue) { + return Recur.fromData(aValue); + }, + undecorate: function undecorate(aRecur) { + return aRecur.toJSON(); + } + }, + time: { + fromICAL: function fromICAL(aValue) { + // from: MMHHSS(Z)? + // to: HH:MM:SS(Z)? + if (aValue.length < 6) { + // TODO: parser exception? + return aValue; + } + + // HH::MM::SSZ? + var result = aValue.slice(0, 2) + ':' + aValue.slice(2, 4) + ':' + aValue.slice(4, 6); + if (aValue[6] === 'Z') { + result += 'Z'; + } + return result; + }, + toICAL: function toICAL(aValue) { + // from: HH:MM:SS(Z)? + // to: MMHHSS(Z)? + if (aValue.length < 8) { + //TODO: error + return aValue; + } + var result = aValue.slice(0, 2) + aValue.slice(3, 5) + aValue.slice(6, 8); + if (aValue[8] === 'Z') { + result += 'Z'; + } + return result; + } + } + }); + var icalProperties = extend(commonProperties, { + "action": DEFAULT_TYPE_TEXT, + "attach": { + defaultType: "uri" + }, + "attendee": { + defaultType: "cal-address" + }, + "calscale": DEFAULT_TYPE_TEXT, + "class": DEFAULT_TYPE_TEXT, + "comment": DEFAULT_TYPE_TEXT, + "completed": DEFAULT_TYPE_DATETIME, + "contact": DEFAULT_TYPE_TEXT, + "created": DEFAULT_TYPE_DATETIME, + "description": DEFAULT_TYPE_TEXT, + "dtend": DEFAULT_TYPE_DATETIME_DATE, + "dtstamp": DEFAULT_TYPE_DATETIME, + "dtstart": DEFAULT_TYPE_DATETIME_DATE, + "due": DEFAULT_TYPE_DATETIME_DATE, + "duration": { + defaultType: "duration" + }, + "exdate": { + defaultType: "date-time", + allowedTypes: ["date-time", "date"], + multiValue: ',' + }, + "exrule": DEFAULT_TYPE_RECUR, + "freebusy": { + defaultType: "period", + multiValue: "," + }, + "geo": { + defaultType: "float", + structuredValue: ";" + }, + "last-modified": DEFAULT_TYPE_DATETIME, + "location": DEFAULT_TYPE_TEXT, + "method": DEFAULT_TYPE_TEXT, + "organizer": { + defaultType: "cal-address" + }, + "percent-complete": DEFAULT_TYPE_INTEGER, + "priority": DEFAULT_TYPE_INTEGER, + "prodid": DEFAULT_TYPE_TEXT, + "related-to": DEFAULT_TYPE_TEXT, + "repeat": DEFAULT_TYPE_INTEGER, + "rdate": { + defaultType: "date-time", + allowedTypes: ["date-time", "date", "period"], + multiValue: ',', + detectType: function detectType(string) { + if (string.indexOf('/') !== -1) { + return 'period'; + } + return string.indexOf('T') === -1 ? 'date' : 'date-time'; + } + }, + "recurrence-id": DEFAULT_TYPE_DATETIME_DATE, + "resources": DEFAULT_TYPE_TEXT_MULTI, + "request-status": DEFAULT_TYPE_TEXT_STRUCTURED, + "rrule": DEFAULT_TYPE_RECUR, + "sequence": DEFAULT_TYPE_INTEGER, + "status": DEFAULT_TYPE_TEXT, + "summary": DEFAULT_TYPE_TEXT, + "transp": DEFAULT_TYPE_TEXT, + "trigger": { + defaultType: "duration", + allowedTypes: ["duration", "date-time"] + }, + "tzoffsetfrom": DEFAULT_TYPE_UTCOFFSET, + "tzoffsetto": DEFAULT_TYPE_UTCOFFSET, + "tzurl": DEFAULT_TYPE_URI, + "tzid": DEFAULT_TYPE_TEXT, + "tzname": DEFAULT_TYPE_TEXT + }); + + // When adding a value here, be sure to add it to the parameter types! + var vcardValues = extend(commonValues, { + text: createTextType(FROM_VCARD_NEWLINE, TO_VCARD_NEWLINE), + uri: createTextType(FROM_VCARD_NEWLINE, TO_VCARD_NEWLINE), + date: { + decorate: function decorate(aValue) { + return VCardTime.fromDateAndOrTimeString(aValue, "date"); + }, + undecorate: function undecorate(aValue) { + return aValue.toString(); + }, + fromICAL: function fromICAL(aValue) { + if (aValue.length == 8) { + return icalValues.date.fromICAL(aValue); + } else if (aValue[0] == '-' && aValue.length == 6) { + return aValue.slice(0, 4) + '-' + aValue.slice(4); + } else { + return aValue; + } + }, + toICAL: function toICAL(aValue) { + if (aValue.length == 10) { + return icalValues.date.toICAL(aValue); + } else if (aValue[0] == '-' && aValue.length == 7) { + return aValue.slice(0, 4) + aValue.slice(5); + } else { + return aValue; + } + } + }, + time: { + decorate: function decorate(aValue) { + return VCardTime.fromDateAndOrTimeString("T" + aValue, "time"); + }, + undecorate: function undecorate(aValue) { + return aValue.toString(); + }, + fromICAL: function fromICAL(aValue) { + var splitzone = vcardValues.time._splitZone(aValue, true); + var zone = splitzone[0], + value = splitzone[1]; + + //console.log("SPLIT: ",splitzone); + + if (value.length == 6) { + value = value.slice(0, 2) + ':' + value.slice(2, 4) + ':' + value.slice(4, 6); + } else if (value.length == 4 && value[0] != '-') { + value = value.slice(0, 2) + ':' + value.slice(2, 4); + } else if (value.length == 5) { + value = value.slice(0, 3) + ':' + value.slice(3, 5); + } + if (zone.length == 5 && (zone[0] == '-' || zone[0] == '+')) { + zone = zone.slice(0, 3) + ':' + zone.slice(3); + } + return value + zone; + }, + toICAL: function toICAL(aValue) { + var splitzone = vcardValues.time._splitZone(aValue); + var zone = splitzone[0], + value = splitzone[1]; + if (value.length == 8) { + value = value.slice(0, 2) + value.slice(3, 5) + value.slice(6, 8); + } else if (value.length == 5 && value[0] != '-') { + value = value.slice(0, 2) + value.slice(3, 5); + } else if (value.length == 6) { + value = value.slice(0, 3) + value.slice(4, 6); + } + if (zone.length == 6 && (zone[0] == '-' || zone[0] == '+')) { + zone = zone.slice(0, 3) + zone.slice(4); + } + return value + zone; + }, + _splitZone: function _splitZone(aValue, isFromIcal) { + var lastChar = aValue.length - 1; + var signChar = aValue.length - (isFromIcal ? 5 : 6); + var sign = aValue[signChar]; + var zone, value; + if (aValue[lastChar] == 'Z') { + zone = aValue[lastChar]; + value = aValue.slice(0, Math.max(0, lastChar)); + } else if (aValue.length > 6 && (sign == '-' || sign == '+')) { + zone = aValue.slice(signChar); + value = aValue.slice(0, Math.max(0, signChar)); + } else { + zone = ""; + value = aValue; + } + return [zone, value]; + } + }, + "date-time": { + decorate: function decorate(aValue) { + return VCardTime.fromDateAndOrTimeString(aValue, "date-time"); + }, + undecorate: function undecorate(aValue) { + return aValue.toString(); + }, + fromICAL: function fromICAL(aValue) { + return vcardValues['date-and-or-time'].fromICAL(aValue); + }, + toICAL: function toICAL(aValue) { + return vcardValues['date-and-or-time'].toICAL(aValue); + } + }, + "date-and-or-time": { + decorate: function decorate(aValue) { + return VCardTime.fromDateAndOrTimeString(aValue, "date-and-or-time"); + }, + undecorate: function undecorate(aValue) { + return aValue.toString(); + }, + fromICAL: function fromICAL(aValue) { + var parts = aValue.split('T'); + return (parts[0] ? vcardValues.date.fromICAL(parts[0]) : '') + (parts[1] ? 'T' + vcardValues.time.fromICAL(parts[1]) : ''); + }, + toICAL: function toICAL(aValue) { + var parts = aValue.split('T'); + return vcardValues.date.toICAL(parts[0]) + (parts[1] ? 'T' + vcardValues.time.toICAL(parts[1]) : ''); + } + }, + timestamp: icalValues['date-time'], + "language-tag": { + matches: /^[a-zA-Z0-9-]+$/ // Could go with a more strict regex here + }, + "phone-number": { + fromICAL: function fromICAL(aValue) { + return Array.from(aValue).filter(function (c) { + return c === '\\' ? undefined : c; + }).join(''); + }, + toICAL: function toICAL(aValue) { + return Array.from(aValue).map(function (c) { + return c === ',' || c === ";" ? '\\' + c : c; + }).join(''); + } + } + }); + var vcardParams = { + "type": { + valueType: "text", + multiValue: "," + }, + "value": { + // since the value here is a 'type' lowercase is used. + values: ["text", "uri", "date", "time", "date-time", "date-and-or-time", "timestamp", "boolean", "integer", "float", "utc-offset", "language-tag"], + allowXName: true, + allowIanaToken: true + } + }; + var vcardProperties = extend(commonProperties, { + "adr": { + defaultType: "text", + structuredValue: ";", + multiValue: "," + }, + "anniversary": DEFAULT_TYPE_DATE_ANDOR_TIME, + "bday": DEFAULT_TYPE_DATE_ANDOR_TIME, + "caladruri": DEFAULT_TYPE_URI, + "caluri": DEFAULT_TYPE_URI, + "clientpidmap": DEFAULT_TYPE_TEXT_STRUCTURED, + "email": DEFAULT_TYPE_TEXT, + "fburl": DEFAULT_TYPE_URI, + "fn": DEFAULT_TYPE_TEXT, + "gender": DEFAULT_TYPE_TEXT_STRUCTURED, + "geo": DEFAULT_TYPE_URI, + "impp": DEFAULT_TYPE_URI, + "key": DEFAULT_TYPE_URI, + "kind": DEFAULT_TYPE_TEXT, + "lang": { + defaultType: "language-tag" + }, + "logo": DEFAULT_TYPE_URI, + "member": DEFAULT_TYPE_URI, + "n": { + defaultType: "text", + structuredValue: ";", + multiValue: "," + }, + "nickname": DEFAULT_TYPE_TEXT_MULTI, + "note": DEFAULT_TYPE_TEXT, + "org": { + defaultType: "text", + structuredValue: ";" + }, + "photo": DEFAULT_TYPE_URI, + "related": DEFAULT_TYPE_URI, + "rev": { + defaultType: "timestamp" + }, + "role": DEFAULT_TYPE_TEXT, + "sound": DEFAULT_TYPE_URI, + "source": DEFAULT_TYPE_URI, + "tel": { + defaultType: "uri", + allowedTypes: ["uri", "text"] + }, + "title": DEFAULT_TYPE_TEXT, + "tz": { + defaultType: "text", + allowedTypes: ["text", "utc-offset", "uri"] + }, + "xml": DEFAULT_TYPE_TEXT + }); + var vcard3Values = extend(commonValues, { + binary: icalValues.binary, + date: vcardValues.date, + "date-time": vcardValues["date-time"], + "phone-number": vcardValues["phone-number"], + uri: icalValues.uri, + text: vcardValues.text, + time: icalValues.time, + vcard: icalValues.text, + "utc-offset": { + toICAL: function toICAL(aValue) { + return aValue.slice(0, 7); + }, + fromICAL: function fromICAL(aValue) { + return aValue.slice(0, 7); + }, + decorate: function decorate(aValue) { + return UtcOffset.fromString(aValue); + }, + undecorate: function undecorate(aValue) { + return aValue.toString(); + } + } + }); + var vcard3Params = { + "type": { + valueType: "text", + multiValue: "," + }, + "value": { + // since the value here is a 'type' lowercase is used. + values: ["text", "uri", "date", "date-time", "phone-number", "time", "boolean", "integer", "float", "utc-offset", "vcard", "binary"], + allowXName: true, + allowIanaToken: true + } + }; + var vcard3Properties = extend(commonProperties, { + fn: DEFAULT_TYPE_TEXT, + n: { + defaultType: "text", + structuredValue: ";", + multiValue: "," + }, + nickname: DEFAULT_TYPE_TEXT_MULTI, + photo: { + defaultType: "binary", + allowedTypes: ["binary", "uri"] + }, + bday: { + defaultType: "date-time", + allowedTypes: ["date-time", "date"], + detectType: function detectType(string) { + return string.indexOf('T') === -1 ? 'date' : 'date-time'; + } + }, + adr: { + defaultType: "text", + structuredValue: ";", + multiValue: "," + }, + label: DEFAULT_TYPE_TEXT, + tel: { + defaultType: "phone-number" + }, + email: DEFAULT_TYPE_TEXT, + mailer: DEFAULT_TYPE_TEXT, + tz: { + defaultType: "utc-offset", + allowedTypes: ["utc-offset", "text"] + }, + geo: { + defaultType: "float", + structuredValue: ";" + }, + title: DEFAULT_TYPE_TEXT, + role: DEFAULT_TYPE_TEXT, + logo: { + defaultType: "binary", + allowedTypes: ["binary", "uri"] + }, + agent: { + defaultType: "vcard", + allowedTypes: ["vcard", "text", "uri"] + }, + org: DEFAULT_TYPE_TEXT_STRUCTURED, + note: DEFAULT_TYPE_TEXT_MULTI, + prodid: DEFAULT_TYPE_TEXT, + rev: { + defaultType: "date-time", + allowedTypes: ["date-time", "date"], + detectType: function detectType(string) { + return string.indexOf('T') === -1 ? 'date' : 'date-time'; + } + }, + "sort-string": DEFAULT_TYPE_TEXT, + sound: { + defaultType: "binary", + allowedTypes: ["binary", "uri"] + }, + "class": DEFAULT_TYPE_TEXT, + key: { + defaultType: "binary", + allowedTypes: ["binary", "text"] + } + }); + + /** + * iCalendar design set + * @type {designSet} + */ + var icalSet = { + name: "ical", + value: icalValues, + param: icalParams, + property: icalProperties, + propertyGroups: false + }; + + /** + * vCard 4.0 design set + * @type {designSet} + */ + var vcardSet = { + name: "vcard4", + value: vcardValues, + param: vcardParams, + property: vcardProperties, + propertyGroups: true + }; + + /** + * vCard 3.0 design set + * @type {designSet} + */ + var vcard3Set = { + name: "vcard3", + value: vcard3Values, + param: vcard3Params, + property: vcard3Properties, + propertyGroups: true + }; + + /** + * The design data, used by the parser to determine types for properties and + * other metadata needed to produce correct jCard/jCal data. + * + * @alias ICAL.design + * @exports module:ICAL.design + */ + var design = { + /** + * Can be set to false to make the parser more lenient. + */ + strict: true, + /** + * The default set for new properties and components if none is specified. + * @type {designSet} + */ + defaultSet: icalSet, + /** + * The default type for unknown properties + * @type {String} + */ + defaultType: 'unknown', + /** + * Holds the design set for known top-level components + * + * @type {Object} + * @property {designSet} vcard vCard VCARD + * @property {designSet} vevent iCalendar VEVENT + * @property {designSet} vtodo iCalendar VTODO + * @property {designSet} vjournal iCalendar VJOURNAL + * @property {designSet} valarm iCalendar VALARM + * @property {designSet} vtimezone iCalendar VTIMEZONE + * @property {designSet} daylight iCalendar DAYLIGHT + * @property {designSet} standard iCalendar STANDARD + * + * @example + * let propertyName = 'fn'; + * let componentDesign = ICAL.design.components.vcard; + * let propertyDetails = componentDesign.property[propertyName]; + * if (propertyDetails.defaultType == 'text') { + * // Yep, sure is... + * } + */ + components: { + vcard: vcardSet, + vcard3: vcard3Set, + vevent: icalSet, + vtodo: icalSet, + vjournal: icalSet, + valarm: icalSet, + vtimezone: icalSet, + daylight: icalSet, + standard: icalSet + }, + /** + * The design set for iCalendar (rfc5545/rfc7265) components. + * @type {designSet} + */ + icalendar: icalSet, + /** + * The design set for vCard (rfc6350/rfc7095) components. + * @type {designSet} + */ + vcard: vcardSet, + /** + * The design set for vCard (rfc2425/rfc2426/rfc7095) components. + * @type {designSet} + */ + vcard3: vcard3Set, + /** + * Gets the design set for the given component name. + * + * @param {String} componentName The name of the component + * @return {designSet} The design set for the component + */ + getDesignSet: function getDesignSet(componentName) { + var isInDesign = componentName && componentName in design.components; + return isInDesign ? design.components[componentName] : design.defaultSet; + } + }; + + /** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * + * @ignore + * @typedef {import("./types.js").designSet} designSet + * Imports the 'designSet' type from the "types.js" module + */ + + var LINE_ENDING = '\r\n'; + var DEFAULT_VALUE_TYPE = 'unknown'; + var RFC6868_REPLACE_MAP = { + '"': "^'", + "\n": "^n", + "^": "^^" + }; + + /** + * Convert a full jCal/jCard array into a iCalendar/vCard string. + * + * @function ICAL.stringify + * @variation function + * @param {Array} jCal The jCal/jCard document + * @return {String} The stringified iCalendar/vCard document + */ + function stringify(jCal) { + if (typeof jCal[0] == "string") { + // This is a single component + jCal = [jCal]; + } + var i = 0; + var len = jCal.length; + var result = ''; + for (; i < len; i++) { + result += stringify.component(jCal[i]) + LINE_ENDING; + } + return result; + } + + /** + * Converts an jCal component array into a ICAL string. + * Recursive will resolve sub-components. + * + * Exact component/property order is not saved all + * properties will come before subcomponents. + * + * @function ICAL.stringify.component + * @param {Array} component + * jCal/jCard fragment of a component + * @param {designSet} designSet + * The design data to use for this component + * @return {String} The iCalendar/vCard string + */ + stringify.component = function (component, designSet) { + var name = component[0].toUpperCase(); + var result = 'BEGIN:' + name + LINE_ENDING; + var props = component[1]; + var propIdx = 0; + var propLen = props.length; + var designSetName = component[0]; + // rfc6350 requires that in vCard 4.0 the first component is the VERSION + // component with as value 4.0, note that 3.0 does not have this requirement. + if (designSetName === 'vcard' && component[1].length > 0 && !(component[1][0][0] === "version" && component[1][0][3] === "4.0")) { + designSetName = "vcard3"; + } + designSet = designSet || design.getDesignSet(designSetName); + for (; propIdx < propLen; propIdx++) { + result += stringify.property(props[propIdx], designSet) + LINE_ENDING; + } + + // Ignore subcomponents if none exist, e.g. in vCard. + var comps = component[2] || []; + var compIdx = 0; + var compLen = comps.length; + for (; compIdx < compLen; compIdx++) { + result += stringify.component(comps[compIdx], designSet) + LINE_ENDING; + } + result += 'END:' + name; + return result; + }; + + /** + * Converts a single jCal/jCard property to a iCalendar/vCard string. + * + * @function ICAL.stringify.property + * @param {Array} property + * jCal/jCard property array + * @param {designSet} designSet + * The design data to use for this property + * @param {Boolean} noFold + * If true, the line is not folded + * @return {String} The iCalendar/vCard string + */ + stringify.property = function (property, designSet, noFold) { + var name = property[0].toUpperCase(); + var jsName = property[0]; + var params = property[1]; + if (!designSet) { + designSet = design.defaultSet; + } + var groupName = params.group; + var line; + if (designSet.propertyGroups && groupName) { + line = groupName.toUpperCase() + "." + name; + } else { + line = name; + } + var _loop = function _loop() { + var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), + paramName = _Object$entries$_i[0], + value = _Object$entries$_i[1]; + if (designSet.propertyGroups && paramName == 'group') { + return 1; // continue + } + var paramDesign = designSet.param[paramName]; + var multiValue = paramDesign && paramDesign.multiValue; + if (multiValue && Array.isArray(value)) { + value = value.map(function (val) { + val = stringify._rfc6868Unescape(val); + val = stringify.paramPropertyValue(val, paramDesign.multiValueSeparateDQuote); + return val; + }); + value = stringify.multiValue(value, multiValue, "unknown", null, designSet); + } else { + value = stringify._rfc6868Unescape(value); + value = stringify.paramPropertyValue(value); + } + line += ';' + paramName.toUpperCase() + '=' + value; + }; + for (var _i = 0, _Object$entries = Object.entries(params); _i < _Object$entries.length; _i++) { + if (_loop()) continue; + } + if (property.length === 3) { + // If there are no values, we must assume a blank value + return line + ':'; + } + var valueType = property[2]; + var propDetails; + var multiValue = false; + var structuredValue = false; + var isDefault = false; + if (jsName in designSet.property) { + propDetails = designSet.property[jsName]; + if ('multiValue' in propDetails) { + multiValue = propDetails.multiValue; + } + if ('structuredValue' in propDetails && Array.isArray(property[3])) { + structuredValue = propDetails.structuredValue; + } + if ('defaultType' in propDetails) { + if (valueType === propDetails.defaultType) { + isDefault = true; + } + } else { + if (valueType === DEFAULT_VALUE_TYPE) { + isDefault = true; + } + } + } else { + if (valueType === DEFAULT_VALUE_TYPE) { + isDefault = true; + } + } + + // push the VALUE property if type is not the default + // for the current property. + if (!isDefault) { + // value will never contain ;/:/, so we don't escape it here. + line += ';VALUE=' + valueType.toUpperCase(); + } + line += ':'; + if (multiValue && structuredValue) { + line += stringify.multiValue(property[3], structuredValue, valueType, multiValue, designSet, structuredValue); + } else if (multiValue) { + line += stringify.multiValue(property.slice(3), multiValue, valueType, null, designSet, false); + } else if (structuredValue) { + line += stringify.multiValue(property[3], structuredValue, valueType, null, designSet, structuredValue); + } else { + line += stringify.value(property[3], valueType, designSet, false); + } + return noFold ? line : foldline(line); + }; + + /** + * Handles escaping of property values that may contain: + * + * COLON (:), SEMICOLON (;), or COMMA (,) + * + * If any of the above are present the result is wrapped + * in double quotes. + * + * @function ICAL.stringify.paramPropertyValue + * @param {String} value Raw property value + * @param {boolean} force If value should be escaped even when unnecessary + * @return {String} Given or escaped value when needed + */ + stringify.paramPropertyValue = function (value, force) { + if (!force && value.indexOf(',') === -1 && value.indexOf(':') === -1 && value.indexOf(';') === -1) { + return value; + } + return '"' + value + '"'; + }; + + /** + * Converts an array of ical values into a single + * string based on a type and a delimiter value (like ","). + * + * @function ICAL.stringify.multiValue + * @param {Array} values List of values to convert + * @param {String} delim Used to join the values (",", ";", ":") + * @param {String} type Lowecase ical value type + * (like boolean, date-time, etc..) + * @param {?String} innerMulti If set, each value will again be processed + * Used for structured values + * @param {designSet} designSet + * The design data to use for this property + * + * @return {String} iCalendar/vCard string for value + */ + stringify.multiValue = function (values, delim, type, innerMulti, designSet, structuredValue) { + var result = ''; + var len = values.length; + var i = 0; + for (; i < len; i++) { + if (innerMulti && Array.isArray(values[i])) { + result += stringify.multiValue(values[i], innerMulti, type, null, designSet, structuredValue); + } else { + result += stringify.value(values[i], type, designSet, structuredValue); + } + if (i !== len - 1) { + result += delim; + } + } + return result; + }; + + /** + * Processes a single ical value runs the associated "toICAL" method from the + * design value type if available to convert the value. + * + * @function ICAL.stringify.value + * @param {String|Number} value A formatted value + * @param {String} type Lowercase iCalendar/vCard value type + * (like boolean, date-time, etc..) + * @return {String} iCalendar/vCard value for single value + */ + stringify.value = function (value, type, designSet, structuredValue) { + if (type in designSet.value && 'toICAL' in designSet.value[type]) { + return designSet.value[type].toICAL(value, structuredValue); + } + return value; + }; + + /** + * Internal helper for rfc6868. Exposing this on ICAL.stringify so that + * hackers can disable the rfc6868 parsing if the really need to. + * + * @param {String} val The value to unescape + * @return {String} The escaped value + */ + stringify._rfc6868Unescape = function (val) { + return val.replace(/[\n^"]/g, function (x) { + return RFC6868_REPLACE_MAP[x]; + }); + }; + + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + + var NAME_INDEX$1 = 0; + var PROP_INDEX = 1; + var TYPE_INDEX = 2; + var VALUE_INDEX = 3; + + /** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * @ignore + * @typedef {import("./types.js").designSet} designSet + * Imports the 'designSet' type from the "types.js" module + * @typedef {import("./types.js").Geo} Geo + * Imports the 'Geo' type from the "types.js" module + */ + + /** + * Provides a layer on top of the raw jCal object for manipulating a single property, with its + * parameters and value. + * + * @memberof ICAL + */ + var Property = /*#__PURE__*/function () { + /** + * Creates a new ICAL.Property instance. + * + * It is important to note that mutations done in the wrapper directly mutate the jCal object used + * to initialize. + * + * Can also be used to create new properties by passing the name of the property (as a String). + * + * @param {Array|String} jCal Raw jCal representation OR the new name of the property + * @param {Component=} parent Parent component + */ + function Property(jCal, parent) { + _classCallCheck(this, Property); + this._parent = parent || null; + if (typeof jCal === 'string') { + // We are creating the property by name and need to detect the type + this.jCal = [jCal, {}, design.defaultType]; + this.jCal[TYPE_INDEX] = this.getDefaultType(); + } else { + this.jCal = jCal; + } + this._updateType(); + } + + /** + * The value type for this property + * @type {String} + */ + return _createClass(Property, [{ + key: "type", + get: function get() { + return this.jCal[TYPE_INDEX]; + } + + /** + * The name of this property, in lowercase. + * @type {String} + */ + }, { + key: "name", + get: function get() { + return this.jCal[NAME_INDEX$1]; + } + + /** + * The parent component for this property. + * @type {Component} + */ + }, { + key: "parent", + get: function get() { + return this._parent; + }, + set: function set(p) { + // Before setting the parent, check if the design set has changed. If it + // has, we later need to update the type if it was unknown before. + var designSetChanged = !this._parent || p && p._designSet != this._parent._designSet; + this._parent = p; + if (this.type == design.defaultType && designSetChanged) { + this.jCal[TYPE_INDEX] = this.getDefaultType(); + this._updateType(); + } + } + + /** + * The design set for this property, e.g. icalendar vs vcard + * + * @type {designSet} + * @private + */ + }, { + key: "_designSet", + get: function get() { + return this.parent ? this.parent._designSet : design.defaultSet; + } + + /** + * Updates the type metadata from the current jCal type and design set. + * + * @private + */ + }, { + key: "_updateType", + value: function _updateType() { + var designSet = this._designSet; + if (this.type in designSet.value) { + if ('decorate' in designSet.value[this.type]) { + this.isDecorated = true; + } else { + this.isDecorated = false; + } + if (this.name in designSet.property) { + this.isMultiValue = 'multiValue' in designSet.property[this.name]; + this.isStructuredValue = 'structuredValue' in designSet.property[this.name]; + } + } + } + + /** + * Hydrate a single value. The act of hydrating means turning the raw jCal + * value into a potentially wrapped object, for example {@link ICAL.Time}. + * + * @private + * @param {Number} index The index of the value to hydrate + * @return {?Object} The decorated value. + */ + }, { + key: "_hydrateValue", + value: function _hydrateValue(index) { + if (this._values && this._values[index]) { + return this._values[index]; + } + + // for the case where there is no value. + if (this.jCal.length <= VALUE_INDEX + index) { + return null; + } + if (this.isDecorated) { + if (!this._values) { + this._values = []; + } + return this._values[index] = this._decorate(this.jCal[VALUE_INDEX + index]); + } else { + return this.jCal[VALUE_INDEX + index]; + } + } + + /** + * Decorate a single value, returning its wrapped object. This is used by + * the hydrate function to actually wrap the value. + * + * @private + * @param {?} value The value to decorate + * @return {Object} The decorated value + */ + }, { + key: "_decorate", + value: function _decorate(value) { + return this._designSet.value[this.type].decorate(value, this); + } + + /** + * Undecorate a single value, returning its raw jCal data. + * + * @private + * @param {Object} value The value to undecorate + * @return {?} The undecorated value + */ + }, { + key: "_undecorate", + value: function _undecorate(value) { + return this._designSet.value[this.type].undecorate(value, this); + } + + /** + * Sets the value at the given index while also hydrating it. The passed + * value can either be a decorated or undecorated value. + * + * @private + * @param {?} value The value to set + * @param {Number} index The index to set it at + */ + }, { + key: "_setDecoratedValue", + value: function _setDecoratedValue(value, index) { + if (!this._values) { + this._values = []; + } + if (_typeof(value) === 'object' && 'icaltype' in value) { + // decorated value + this.jCal[VALUE_INDEX + index] = this._undecorate(value); + this._values[index] = value; + } else { + // undecorated value + this.jCal[VALUE_INDEX + index] = value; + this._values[index] = this._decorate(value); + } + } + + /** + * Gets a parameter on the property. + * + * @param {String} name Parameter name (lowercase) + * @return {Array|String} Parameter value + */ + }, { + key: "getParameter", + value: function getParameter(name) { + if (name in this.jCal[PROP_INDEX]) { + return this.jCal[PROP_INDEX][name]; + } else { + return undefined; + } + } + + /** + * Gets first parameter on the property. + * + * @param {String} name Parameter name (lowercase) + * @return {String} Parameter value + */ + }, { + key: "getFirstParameter", + value: function getFirstParameter(name) { + var parameters = this.getParameter(name); + if (Array.isArray(parameters)) { + return parameters[0]; + } + return parameters; + } + + /** + * Sets a parameter on the property. + * + * @param {String} name The parameter name + * @param {Array|String} value The parameter value + */ + }, { + key: "setParameter", + value: function setParameter(name, value) { + var lcname = name.toLowerCase(); + if (typeof value === "string" && lcname in this._designSet.param && 'multiValue' in this._designSet.param[lcname]) { + value = [value]; + } + this.jCal[PROP_INDEX][name] = value; + } + + /** + * Removes a parameter + * + * @param {String} name The parameter name + */ + }, { + key: "removeParameter", + value: function removeParameter(name) { + delete this.jCal[PROP_INDEX][name]; + } + + /** + * Get the default type based on this property's name. + * + * @return {String} The default type for this property + */ + }, { + key: "getDefaultType", + value: function getDefaultType() { + var name = this.jCal[NAME_INDEX$1]; + var designSet = this._designSet; + if (name in designSet.property) { + var details = designSet.property[name]; + if ('defaultType' in details) { + return details.defaultType; + } + } + return design.defaultType; + } + + /** + * Sets type of property and clears out any existing values of the current + * type. + * + * @param {String} type New iCAL type (see design.*.values) + */ + }, { + key: "resetType", + value: function resetType(type) { + this.removeAllValues(); + this.jCal[TYPE_INDEX] = type; + this._updateType(); + } + + /** + * Finds the first property value. + * + * @return {Binary | Duration | Period | + * Recur | Time | UtcOffset | Geo | string | null} First property value + */ + }, { + key: "getFirstValue", + value: function getFirstValue() { + return this._hydrateValue(0); + } + + /** + * Gets all values on the property. + * + * NOTE: this creates an array during each call. + * + * @return {Array} List of values + */ + }, { + key: "getValues", + value: function getValues() { + var len = this.jCal.length - VALUE_INDEX; + if (len < 1) { + // it is possible for a property to have no value. + return []; + } + var i = 0; + var result = []; + for (; i < len; i++) { + result[i] = this._hydrateValue(i); + } + return result; + } + + /** + * Removes all values from this property + */ + }, { + key: "removeAllValues", + value: function removeAllValues() { + if (this._values) { + this._values.length = 0; + } + this.jCal.length = 3; + } + + /** + * Sets the values of the property. Will overwrite the existing values. + * This can only be used for multi-value properties. + * + * @param {Array} values An array of values + */ + }, { + key: "setValues", + value: function setValues(values) { + if (!this.isMultiValue) { + throw new Error(this.name + ': does not not support mulitValue.\n' + 'override isMultiValue'); + } + var len = values.length; + var i = 0; + this.removeAllValues(); + if (len > 0 && _typeof(values[0]) === 'object' && 'icaltype' in values[0]) { + this.resetType(values[0].icaltype); + } + if (this.isDecorated) { + for (; i < len; i++) { + this._setDecoratedValue(values[i], i); + } + } else { + for (; i < len; i++) { + this.jCal[VALUE_INDEX + i] = values[i]; + } + } + } + + /** + * Sets the current value of the property. If this is a multi-value + * property, all other values will be removed. + * + * @param {String|Object} value New property value. + */ + }, { + key: "setValue", + value: function setValue(value) { + this.removeAllValues(); + if (_typeof(value) === 'object' && 'icaltype' in value) { + this.resetType(value.icaltype); + } + if (this.isDecorated) { + this._setDecoratedValue(value, 0); + } else { + this.jCal[VALUE_INDEX] = value; + } + } + + /** + * Returns the Object representation of this component. The returned object + * is a live jCal object and should be cloned if modified. + * @return {Object} + */ + }, { + key: "toJSON", + value: function toJSON() { + return this.jCal; + } + + /** + * The string representation of this component. + * @return {String} + */ + }, { + key: "toICALString", + value: function toICALString() { + return stringify.property(this.jCal, this._designSet, true); + } + }], [{ + key: "fromString", + value: + /** + * Create an {@link ICAL.Property} by parsing the passed iCalendar string. + * + * @param {String} str The iCalendar string to parse + * @param {designSet=} designSet The design data to use for this property + * @return {Property} The created iCalendar property + */ + function fromString(str, designSet) { + return new Property(parse.property(str, designSet)); + } + }]); + }(); + + /** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * @ignore + * @typedef {import("./types.js").designSet} designSet + * Imports the 'designSet' type from the "types.js" module + * @typedef {import("./types.js").Geo} Geo + * Imports the 'Geo' type from the "types.js" module + */ + + var NAME_INDEX = 0; + var PROPERTY_INDEX = 1; + var COMPONENT_INDEX = 2; + var PROPERTY_NAME_INDEX = 0; + var PROPERTY_VALUE_INDEX = 3; + + /** + * Wraps a jCal component, adding convenience methods to add, remove and update subcomponents and + * properties. + * + * @memberof ICAL + */ + var Component = /*#__PURE__*/function () { + /** + * Creates a new Component instance. + * + * @param {Array|String} jCal Raw jCal component data OR name of new + * component + * @param {Component=} parent Parent component to associate + */ + function Component(jCal, parent) { + _classCallCheck(this, Component); + /** + * Hydrated properties are inserted into the _properties array at the same + * position as in the jCal array, so it is possible that the array contains + * undefined values for unhydrdated properties. To avoid iterating the + * array when checking if all properties have been hydrated, we save the + * count here. + * + * @type {Number} + * @private + */ + _defineProperty(this, "_hydratedPropertyCount", 0); + /** + * The same count as for _hydratedPropertyCount, but for subcomponents + * + * @type {Number} + * @private + */ + _defineProperty(this, "_hydratedComponentCount", 0); + /** + * A cache of hydrated time zone objects which may be used by consumers, keyed + * by time zone ID. + * + * @type {Map} + * @private + */ + _defineProperty(this, "_timezoneCache", null); + /** + * @private + */ + _defineProperty(this, "_components", null); + /** + * @private + */ + _defineProperty(this, "_properties", null); + if (typeof jCal === 'string') { + // jCal spec (name, properties, components) + jCal = [jCal, [], []]; + } + + // mostly for legacy reasons. + this.jCal = jCal; + this.parent = parent || null; + if (!this.parent && this.name === 'vcalendar') { + this._timezoneCache = new Map(); + } + } + return _createClass(Component, [{ + key: "name", + get: + /** + * The name of this component + * + * @type {String} + */ + function get() { + return this.jCal[NAME_INDEX]; + } + + /** + * The design set for this component, e.g. icalendar vs vcard + * + * @type {designSet} + * @private + */ + }, { + key: "_designSet", + get: function get() { + var parentDesign = this.parent && this.parent._designSet; + if (!parentDesign && this.name == "vcard") { + var _this$jCal$PROPERTY_I; + // We can't decide on vcard3 vs vcard4 just based on the component name, the version number is + // in the version property. We also can't use hydrated properties here because it would lead + // to recursion, but the spec says that the version property needs to be the very first one. + var versionProp = (_this$jCal$PROPERTY_I = this.jCal[PROPERTY_INDEX]) === null || _this$jCal$PROPERTY_I === void 0 ? void 0 : _this$jCal$PROPERTY_I[0]; + if (versionProp && versionProp[PROPERTY_NAME_INDEX] == "version" && versionProp[PROPERTY_VALUE_INDEX] == "3.0") { + return design.getDesignSet("vcard3"); + } + } + return parentDesign || design.getDesignSet(this.name); + } + + /** + * @private + */ + }, { + key: "_hydrateComponent", + value: function _hydrateComponent(index) { + if (!this._components) { + this._components = []; + this._hydratedComponentCount = 0; + } + if (this._components[index]) { + return this._components[index]; + } + var comp = new Component(this.jCal[COMPONENT_INDEX][index], this); + this._hydratedComponentCount++; + return this._components[index] = comp; + } + + /** + * @private + */ + }, { + key: "_hydrateProperty", + value: function _hydrateProperty(index) { + if (!this._properties) { + this._properties = []; + this._hydratedPropertyCount = 0; + } + if (this._properties[index]) { + return this._properties[index]; + } + var prop = new Property(this.jCal[PROPERTY_INDEX][index], this); + this._hydratedPropertyCount++; + return this._properties[index] = prop; + } + + /** + * Finds first sub component, optionally filtered by name. + * + * @param {String=} name Optional name to filter by + * @return {?Component} The found subcomponent + */ + }, { + key: "getFirstSubcomponent", + value: function getFirstSubcomponent(name) { + if (name) { + var i = 0; + var comps = this.jCal[COMPONENT_INDEX]; + var len = comps.length; + for (; i < len; i++) { + if (comps[i][NAME_INDEX] === name) { + var result = this._hydrateComponent(i); + return result; + } + } + } else { + if (this.jCal[COMPONENT_INDEX].length) { + return this._hydrateComponent(0); + } + } + + // ensure we return a value (strict mode) + return null; + } + + /** + * Finds all sub components, optionally filtering by name. + * + * @param {String=} name Optional name to filter by + * @return {Component[]} The found sub components + */ + }, { + key: "getAllSubcomponents", + value: function getAllSubcomponents(name) { + var jCalLen = this.jCal[COMPONENT_INDEX].length; + var i = 0; + if (name) { + var comps = this.jCal[COMPONENT_INDEX]; + var result = []; + for (; i < jCalLen; i++) { + if (name === comps[i][NAME_INDEX]) { + result.push(this._hydrateComponent(i)); + } + } + return result; + } else { + if (!this._components || this._hydratedComponentCount !== jCalLen) { + for (; i < jCalLen; i++) { + this._hydrateComponent(i); + } + } + return this._components || []; + } + } + + /** + * Returns true when a named property exists. + * + * @param {String} name The property name + * @return {Boolean} True, when property is found + */ + }, { + key: "hasProperty", + value: function hasProperty(name) { + var props = this.jCal[PROPERTY_INDEX]; + var len = props.length; + var i = 0; + for (; i < len; i++) { + // 0 is property name + if (props[i][NAME_INDEX] === name) { + return true; + } + } + return false; + } + + /** + * Finds the first property, optionally with the given name. + * + * @param {String=} name Lowercase property name + * @return {?Property} The found property + */ + }, { + key: "getFirstProperty", + value: function getFirstProperty(name) { + if (name) { + var i = 0; + var props = this.jCal[PROPERTY_INDEX]; + var len = props.length; + for (; i < len; i++) { + if (props[i][NAME_INDEX] === name) { + var result = this._hydrateProperty(i); + return result; + } + } + } else { + if (this.jCal[PROPERTY_INDEX].length) { + return this._hydrateProperty(0); + } + } + return null; + } + + /** + * Returns first property's value, if available. + * + * @param {String=} name Lowercase property name + * @return {Binary | Duration | Period | + * Recur | Time | UtcOffset | Geo | string | null} The found property value. + */ + }, { + key: "getFirstPropertyValue", + value: function getFirstPropertyValue(name) { + var prop = this.getFirstProperty(name); + if (prop) { + return prop.getFirstValue(); + } + return null; + } + + /** + * Get all properties in the component, optionally filtered by name. + * + * @param {String=} name Lowercase property name + * @return {Property[]} List of properties + */ + }, { + key: "getAllProperties", + value: function getAllProperties(name) { + var jCalLen = this.jCal[PROPERTY_INDEX].length; + var i = 0; + if (name) { + var props = this.jCal[PROPERTY_INDEX]; + var result = []; + for (; i < jCalLen; i++) { + if (name === props[i][NAME_INDEX]) { + result.push(this._hydrateProperty(i)); + } + } + return result; + } else { + if (!this._properties || this._hydratedPropertyCount !== jCalLen) { + for (; i < jCalLen; i++) { + this._hydrateProperty(i); + } + } + return this._properties || []; + } + } + + /** + * @private + */ + }, { + key: "_removeObjectByIndex", + value: function _removeObjectByIndex(jCalIndex, cache, index) { + cache = cache || []; + // remove cached version + if (cache[index]) { + var obj = cache[index]; + if ("parent" in obj) { + obj.parent = null; + } + } + cache.splice(index, 1); + + // remove it from the jCal + this.jCal[jCalIndex].splice(index, 1); + } + + /** + * @private + */ + }, { + key: "_removeObject", + value: function _removeObject(jCalIndex, cache, nameOrObject) { + var i = 0; + var objects = this.jCal[jCalIndex]; + var len = objects.length; + var cached = this[cache]; + if (typeof nameOrObject === 'string') { + for (; i < len; i++) { + if (objects[i][NAME_INDEX] === nameOrObject) { + this._removeObjectByIndex(jCalIndex, cached, i); + return true; + } + } + } else if (cached) { + for (; i < len; i++) { + if (cached[i] && cached[i] === nameOrObject) { + this._removeObjectByIndex(jCalIndex, cached, i); + return true; + } + } + } + return false; + } + + /** + * @private + */ + }, { + key: "_removeAllObjects", + value: function _removeAllObjects(jCalIndex, cache, name) { + var cached = this[cache]; + + // Unfortunately we have to run through all children to reset their + // parent property. + var objects = this.jCal[jCalIndex]; + var i = objects.length - 1; + + // descending search required because splice + // is used and will effect the indices. + for (; i >= 0; i--) { + if (!name || objects[i][NAME_INDEX] === name) { + this._removeObjectByIndex(jCalIndex, cached, i); + } + } + } + + /** + * Adds a single sub component. + * + * @param {Component} component The component to add + * @return {Component} The passed in component + */ + }, { + key: "addSubcomponent", + value: function addSubcomponent(component) { + if (!this._components) { + this._components = []; + this._hydratedComponentCount = 0; + } + if (component.parent) { + component.parent.removeSubcomponent(component); + } + var idx = this.jCal[COMPONENT_INDEX].push(component.jCal); + this._components[idx - 1] = component; + this._hydratedComponentCount++; + component.parent = this; + return component; + } + + /** + * Removes a single component by name or the instance of a specific + * component. + * + * @param {Component|String} nameOrComp Name of component, or component + * @return {Boolean} True when comp is removed + */ + }, { + key: "removeSubcomponent", + value: function removeSubcomponent(nameOrComp) { + var removed = this._removeObject(COMPONENT_INDEX, '_components', nameOrComp); + if (removed) { + this._hydratedComponentCount--; + } + return removed; + } + + /** + * Removes all components or (if given) all components by a particular + * name. + * + * @param {String=} name Lowercase component name + */ + }, { + key: "removeAllSubcomponents", + value: function removeAllSubcomponents(name) { + var removed = this._removeAllObjects(COMPONENT_INDEX, '_components', name); + this._hydratedComponentCount = 0; + return removed; + } + + /** + * Adds an {@link ICAL.Property} to the component. + * + * @param {Property} property The property to add + * @return {Property} The passed in property + */ + }, { + key: "addProperty", + value: function addProperty(property) { + if (!(property instanceof Property)) { + throw new TypeError('must be instance of ICAL.Property'); + } + if (!this._properties) { + this._properties = []; + this._hydratedPropertyCount = 0; + } + if (property.parent) { + property.parent.removeProperty(property); + } + var idx = this.jCal[PROPERTY_INDEX].push(property.jCal); + this._properties[idx - 1] = property; + this._hydratedPropertyCount++; + property.parent = this; + return property; + } + + /** + * Helper method to add a property with a value to the component. + * + * @param {String} name Property name to add + * @param {String|Number|Object} value Property value + * @return {Property} The created property + */ + }, { + key: "addPropertyWithValue", + value: function addPropertyWithValue(name, value) { + var prop = new Property(name); + prop.setValue(value); + this.addProperty(prop); + return prop; + } + + /** + * Helper method that will update or create a property of the given name + * and sets its value. If multiple properties with the given name exist, + * only the first is updated. + * + * @param {String} name Property name to update + * @param {String|Number|Object} value Property value + * @return {Property} The created property + */ + }, { + key: "updatePropertyWithValue", + value: function updatePropertyWithValue(name, value) { + var prop = this.getFirstProperty(name); + if (prop) { + prop.setValue(value); + } else { + prop = this.addPropertyWithValue(name, value); + } + return prop; + } + + /** + * Removes a single property by name or the instance of the specific + * property. + * + * @param {String|Property} nameOrProp Property name or instance to remove + * @return {Boolean} True, when deleted + */ + }, { + key: "removeProperty", + value: function removeProperty(nameOrProp) { + var removed = this._removeObject(PROPERTY_INDEX, '_properties', nameOrProp); + if (removed) { + this._hydratedPropertyCount--; + } + return removed; + } + + /** + * Removes all properties associated with this component, optionally + * filtered by name. + * + * @param {String=} name Lowercase property name + * @return {Boolean} True, when deleted + */ + }, { + key: "removeAllProperties", + value: function removeAllProperties(name) { + var removed = this._removeAllObjects(PROPERTY_INDEX, '_properties', name); + this._hydratedPropertyCount = 0; + return removed; + } + + /** + * Returns the Object representation of this component. The returned object + * is a live jCal object and should be cloned if modified. + * @return {Object} + */ + }, { + key: "toJSON", + value: function toJSON() { + return this.jCal; + } + + /** + * The string representation of this component. + * @return {String} + */ + }, { + key: "toString", + value: function toString() { + return stringify.component(this.jCal, this._designSet); + } + + /** + * Retrieve a time zone definition from the component tree, if any is present. + * If the tree contains no time zone definitions or the TZID cannot be + * matched, returns null. + * + * @param {String} tzid The ID of the time zone to retrieve + * @return {Timezone} The time zone corresponding to the ID, or null + */ + }, { + key: "getTimeZoneByID", + value: function getTimeZoneByID(tzid) { + // VTIMEZONE components can only appear as a child of the VCALENDAR + // component; walk the tree if we're not the root. + if (this.parent) { + return this.parent.getTimeZoneByID(tzid); + } + + // If there is no time zone cache, we are probably parsing an incomplete + // file and will have no time zone definitions. + if (!this._timezoneCache) { + return null; + } + if (this._timezoneCache.has(tzid)) { + return this._timezoneCache.get(tzid); + } + + // If the time zone is not already cached, hydrate it from the + // subcomponents. + var zones = this.getAllSubcomponents('vtimezone'); + var _iterator = _createForOfIteratorHelper(zones), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var zone = _step.value; + if (zone.getFirstProperty('tzid').getFirstValue() === tzid) { + var hydratedZone = new Timezone({ + component: zone, + tzid: tzid + }); + this._timezoneCache.set(tzid, hydratedZone); + return hydratedZone; + } + } + + // Per the standard, we should always have a time zone defined in a file + // for any referenced TZID, but don't blow up if the file is invalid. + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + return null; + } + }], [{ + key: "fromString", + value: + /** + * Create an {@link ICAL.Component} by parsing the passed iCalendar string. + * + * @param {String} str The iCalendar string to parse + */ + function fromString(str) { + return new Component(parse.component(str)); + } + }]); + }(); + + /** + * Primary class for expanding recurring rules. Can take multiple rrules, rdates, exdate(s) and + * iterate (in order) over each next occurrence. + * + * Once initialized this class can also be serialized saved and continue iteration from the last + * point. + * + * NOTE: it is intended that this class is to be used with {@link ICAL.Event} which handles recurrence + * exceptions. + * + * @example + * // assuming event is a parsed ical component + * var event; + * + * var expand = new ICAL.RecurExpansion({ + * component: event, + * dtstart: event.getFirstPropertyValue('dtstart') + * }); + * + * // remember there are infinite rules so it is a good idea to limit the scope of the iterations + * // then resume later on. + * + * // next is always an ICAL.Time or null + * var next; + * + * while (someCondition && (next = expand.next())) { + * // do something with next + * } + * + * // save instance for later + * var json = JSON.stringify(expand); + * + * //... + * + * // NOTE: if the component's properties have changed you will need to rebuild the class and start + * // over. This only works when the component's recurrence info is the same. + * var expand = new ICAL.RecurExpansion(JSON.parse(json)); + * + * @memberof ICAL + */ + var RecurExpansion = /*#__PURE__*/function () { + /** + * Creates a new ICAL.RecurExpansion instance. + * + * The options object can be filled with the specified initial values. It can also contain + * additional members, as a result of serializing a previous expansion state, as shown in the + * example. + * + * @param {Object} options + * Recurrence expansion options + * @param {Time} options.dtstart + * Start time of the event + * @param {Component=} options.component + * Component for expansion, required if not resuming. + */ + function RecurExpansion(options) { + _classCallCheck(this, RecurExpansion); + /** + * True when iteration is fully completed. + * @type {Boolean} + */ + _defineProperty(this, "complete", false); + /** + * Array of rrule iterators. + * + * @type {RecurIterator[]} + * @private + */ + _defineProperty(this, "ruleIterators", null); + /** + * Array of rdate instances. + * + * @type {Time[]} + * @private + */ + _defineProperty(this, "ruleDates", null); + /** + * Array of exdate instances. + * + * @type {Time[]} + * @private + */ + _defineProperty(this, "exDates", null); + /** + * Current position in ruleDates array. + * @type {Number} + * @private + */ + _defineProperty(this, "ruleDateInc", 0); + /** + * Current position in exDates array + * @type {Number} + * @private + */ + _defineProperty(this, "exDateInc", 0); + /** + * Current negative date. + * + * @type {Time} + * @private + */ + _defineProperty(this, "exDate", null); + /** + * Current additional date. + * + * @type {Time} + * @private + */ + _defineProperty(this, "ruleDate", null); + /** + * Start date of recurring rules. + * + * @type {Time} + */ + _defineProperty(this, "dtstart", null); + /** + * Last expanded time + * + * @type {Time} + */ + _defineProperty(this, "last", null); + this.ruleDates = []; + this.exDates = []; + this.fromData(options); + } + return _createClass(RecurExpansion, [{ + key: "fromData", + value: + /** + * Initialize the recurrence expansion from the data object. The options + * object may also contain additional members, see the + * {@link ICAL.RecurExpansion constructor} for more details. + * + * @param {Object} options + * Recurrence expansion options + * @param {Time} options.dtstart + * Start time of the event + * @param {Component=} options.component + * Component for expansion, required if not resuming. + */ + function fromData(options) { + var start = formatClassType(options.dtstart, Time); + if (!start) { + throw new Error('.dtstart (ICAL.Time) must be given'); + } else { + this.dtstart = start; + } + if (options.component) { + this._init(options.component); + } else { + this.last = formatClassType(options.last, Time) || start.clone(); + if (!options.ruleIterators) { + throw new Error('.ruleIterators or .component must be given'); + } + this.ruleIterators = options.ruleIterators.map(function (item) { + return formatClassType(item, RecurIterator); + }); + this.ruleDateInc = options.ruleDateInc; + this.exDateInc = options.exDateInc; + if (options.ruleDates) { + this.ruleDates = options.ruleDates.map(function (item) { + return formatClassType(item, Time); + }); + this.ruleDate = this.ruleDates[this.ruleDateInc]; + } + if (options.exDates) { + this.exDates = options.exDates.map(function (item) { + return formatClassType(item, Time); + }); + this.exDate = this.exDates[this.exDateInc]; + } + if (typeof options.complete !== 'undefined') { + this.complete = options.complete; + } + } + } + + /** + * Compare two ICAL.Time objects. When the second parameter is a DATE and the first parameter is + * DATE-TIME, strip the time and compare only the days. + * + * @private + * @param {Time} a The one object to compare + * @param {Time} b The other object to compare + */ + }, { + key: "_compare_special", + value: function _compare_special(a, b) { + if (!a.isDate && b.isDate) return new Time({ + year: a.year, + month: a.month, + day: a.day + }).compare(b); + return a.compare(b); + } + + /** + * Retrieve the next occurrence in the series. + * @return {Time} + */ + }, { + key: "next", + value: function next() { + var iter; + var next; + var compare; + var maxTries = 500; + var currentTry = 0; + while (true) { + if (currentTry++ > maxTries) { + throw new Error('max tries have occurred, rule may be impossible to fulfill.'); + } + next = this.ruleDate; + iter = this._nextRecurrenceIter(this.last); + + // no more matches + // because we increment the rule day or rule + // _after_ we choose a value this should be + // the only spot where we need to worry about the + // end of events. + if (!next && !iter) { + // there are no more iterators or rdates + this.complete = true; + break; + } + + // no next rule day or recurrence rule is first. + if (!next || iter && next.compare(iter.last) > 0) { + // must be cloned, recur will reuse the time element. + next = iter.last.clone(); + // move to next so we can continue + iter.next(); + } + + // if the ruleDate is still next increment it. + if (this.ruleDate === next) { + this._nextRuleDay(); + } + this.last = next; + + // check the negative rules + if (this.exDate) { + // EXDATE can be in DATE format, but DTSTART is in DATE-TIME format + compare = this._compare_special(this.last, this.exDate); + if (compare > 0) { + this._nextExDay(); + } + + // if the current rule is excluded skip it. + if (compare === 0) { + this._nextExDay(); + continue; + } + } + + //XXX: The spec states that after we resolve the final + // list of dates we execute exdate this seems somewhat counter + // intuitive to what I have seen most servers do so for now + // I exclude based on the original date not the one that may + // have been modified by the exception. + return this.last; + } + } + + /** + * Converts object into a serialize-able format. This format can be passed + * back into the expansion to resume iteration. + * @return {Object} + */ + }, { + key: "toJSON", + value: function toJSON() { + function toJSON(item) { + return item.toJSON(); + } + var result = Object.create(null); + result.ruleIterators = this.ruleIterators.map(toJSON); + if (this.ruleDates) { + result.ruleDates = this.ruleDates.map(toJSON); + } + if (this.exDates) { + result.exDates = this.exDates.map(toJSON); + } + result.ruleDateInc = this.ruleDateInc; + result.exDateInc = this.exDateInc; + result.last = this.last.toJSON(); + result.dtstart = this.dtstart.toJSON(); + result.complete = this.complete; + return result; + } + + /** + * Extract all dates from the properties in the given component. The + * properties will be filtered by the property name. + * + * @private + * @param {Component} component The component to search in + * @param {String} propertyName The property name to search for + * @return {Time[]} The extracted dates. + */ + }, { + key: "_extractDates", + value: function _extractDates(component, propertyName) { + var result = []; + var props = component.getAllProperties(propertyName); + for (var i = 0, len = props.length; i < len; i++) { + var _iterator = _createForOfIteratorHelper(props[i].getValues()), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var prop = _step.value; + var idx = binsearchInsert(result, prop, function (a, b) { + return a.compare(b); + }); + + // ordered insert + result.splice(idx, 0, prop); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + } + return result; + } + + /** + * Initialize the recurrence expansion. + * + * @private + * @param {Component} component The component to initialize from. + */ + }, { + key: "_init", + value: function _init(component) { + this.ruleIterators = []; + this.last = this.dtstart.clone(); + + // to provide api consistency non-recurring + // events can also use the iterator though it will + // only return a single time. + if (!component.hasProperty('rdate') && !component.hasProperty('rrule') && !component.hasProperty('recurrence-id')) { + this.ruleDate = this.last.clone(); + this.complete = true; + return; + } + if (component.hasProperty('rdate')) { + this.ruleDates = this._extractDates(component, 'rdate'); + + // special hack for cases where first rdate is prior + // to the start date. We only check for the first rdate. + // This is mostly for google's crazy recurring date logic + // (contacts birthdays). + if (this.ruleDates[0] && this.ruleDates[0].compare(this.dtstart) < 0) { + this.ruleDateInc = 0; + this.last = this.ruleDates[0].clone(); + } else { + this.ruleDateInc = binsearchInsert(this.ruleDates, this.last, function (a, b) { + return a.compare(b); + }); + } + this.ruleDate = this.ruleDates[this.ruleDateInc]; + } + if (component.hasProperty('rrule')) { + var rules = component.getAllProperties('rrule'); + var i = 0; + var len = rules.length; + var rule; + var iter; + for (; i < len; i++) { + rule = rules[i].getFirstValue(); + iter = rule.iterator(this.dtstart); + this.ruleIterators.push(iter); + + // increment to the next occurrence so future + // calls to next return times beyond the initial iteration. + // XXX: I find this suspicious might be a bug? + iter.next(); + } + } + if (component.hasProperty('exdate')) { + this.exDates = this._extractDates(component, 'exdate'); + // if we have a .last day we increment the index to beyond it. + // When DTSTART is in DATE-TIME format, EXDATE is in DATE format and EXDATE is + // the date of DTSTART, _compare_special finds this out and compareTime fails. + this.exDateInc = binsearchInsert(this.exDates, this.last, this._compare_special); + this.exDate = this.exDates[this.exDateInc]; + } + } + + /** + * Advance to the next exdate + * @private + */ + }, { + key: "_nextExDay", + value: function _nextExDay() { + this.exDate = this.exDates[++this.exDateInc]; + } + + /** + * Advance to the next rule date + * @private + */ + }, { + key: "_nextRuleDay", + value: function _nextRuleDay() { + this.ruleDate = this.ruleDates[++this.ruleDateInc]; + } + + /** + * Find and return the recurrence rule with the most recent event and + * return it. + * + * @private + * @return {?RecurIterator} Found iterator. + */ + }, { + key: "_nextRecurrenceIter", + value: function _nextRecurrenceIter() { + var iters = this.ruleIterators; + if (iters.length === 0) { + return null; + } + var len = iters.length; + var iter; + var iterTime; + var iterIdx = 0; + var chosenIter; + + // loop through each iterator + for (; iterIdx < len; iterIdx++) { + iter = iters[iterIdx]; + iterTime = iter.last; + + // if iteration is complete + // then we must exclude it from + // the search and remove it. + if (iter.completed) { + len--; + if (iterIdx !== 0) { + iterIdx--; + } + iters.splice(iterIdx, 1); + continue; + } + + // find the most recent possible choice + if (!chosenIter || chosenIter.last.compare(iterTime) > 0) { + // that iterator is saved + chosenIter = iter; + } + } + + // the chosen iterator is returned but not mutated + // this iterator contains the most recent event. + return chosenIter; + } + }]); + }(); + + /** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * @ignore + * @typedef {import("./types.js").frequencyValues} frequencyValues + * Imports the 'frequencyValues' type from the "types.js" module + * @typedef {import("./types.js").occurrenceDetails} occurrenceDetails + * Imports the 'occurrenceDetails' type from the "types.js" module + */ + + /** + * ICAL.js is organized into multiple layers. The bottom layer is a raw jCal + * object, followed by the component/property layer. The highest level is the + * event representation, which this class is part of. See the + * {@tutorial layers} guide for more details. + * + * @memberof ICAL + */ + var Event = /*#__PURE__*/function () { + /** + * Creates a new ICAL.Event instance. + * + * @param {Component=} component The ICAL.Component to base this event on + * @param {Object} [options] Options for this event + * @param {Boolean=} options.strictExceptions When true, will verify exceptions are related by + * their UUID + * @param {Array=} options.exceptions + * Exceptions to this event, either as components or events. If not + * specified exceptions will automatically be set in relation of + * component's parent + */ + function Event(component, options) { + _classCallCheck(this, Event); + /** + * List of related event exceptions. + * + * @type {Event[]} + */ + _defineProperty(this, "exceptions", null); + /** + * When true, will verify exceptions are related by their UUID. + * + * @type {Boolean} + */ + _defineProperty(this, "strictExceptions", false); + if (!(component instanceof Component)) { + options = component; + component = null; + } + if (component) { + this.component = component; + } else { + this.component = new Component('vevent'); + } + this._rangeExceptionCache = Object.create(null); + this.exceptions = Object.create(null); + this.rangeExceptions = []; + if (options && options.strictExceptions) { + this.strictExceptions = options.strictExceptions; + } + if (options && options.exceptions) { + options.exceptions.forEach(this.relateException, this); + } else if (this.component.parent && !this.isRecurrenceException()) { + this.component.parent.getAllSubcomponents('vevent').forEach(function (event) { + if (event.hasProperty('recurrence-id')) { + this.relateException(event); + } + }, this); + } + } + return _createClass(Event, [{ + key: "relateException", + value: + /** + * Relates a given event exception to this object. If the given component + * does not share the UID of this event it cannot be related and will throw + * an exception. + * + * If this component is an exception it cannot have other exceptions + * related to it. + * + * @param {Component|Event} obj Component or event + */ + function relateException(obj) { + if (this.isRecurrenceException()) { + throw new Error('cannot relate exception to exceptions'); + } + if (obj instanceof Component) { + obj = new Event(obj); + } + if (this.strictExceptions && obj.uid !== this.uid) { + throw new Error('attempted to relate unrelated exception'); + } + var id = obj.recurrenceId.toString(); + + // we don't sort or manage exceptions directly + // here the recurrence expander handles that. + this.exceptions[id] = obj; + + // index RANGE=THISANDFUTURE exceptions so we can + // look them up later in getOccurrenceDetails. + if (obj.modifiesFuture()) { + var item = [obj.recurrenceId.toUnixTime(), id]; + + // we keep them sorted so we can find the nearest + // value later on... + var idx = binsearchInsert(this.rangeExceptions, item, compareRangeException); + this.rangeExceptions.splice(idx, 0, item); + } + } + + /** + * Checks if this record is an exception and has the RANGE=THISANDFUTURE + * value. + * + * @return {Boolean} True, when exception is within range + */ + }, { + key: "modifiesFuture", + value: function modifiesFuture() { + if (!this.component.hasProperty('recurrence-id')) { + return false; + } + var range = this.component.getFirstProperty('recurrence-id').getParameter('range'); + return range === Event.THISANDFUTURE; + } + + /** + * Finds the range exception nearest to the given date. + * + * @param {Time} time usually an occurrence time of an event + * @return {?Event} the related event/exception or null + */ + }, { + key: "findRangeException", + value: function findRangeException(time) { + if (!this.rangeExceptions.length) { + return null; + } + var utc = time.toUnixTime(); + var idx = binsearchInsert(this.rangeExceptions, [utc], compareRangeException); + idx -= 1; + + // occurs before + if (idx < 0) { + return null; + } + var rangeItem = this.rangeExceptions[idx]; + + /* c8 ignore next 4 */ + if (utc < rangeItem[0]) { + // sanity check only + return null; + } + return rangeItem[1]; + } + + /** + * Returns the occurrence details based on its start time. If the + * occurrence has an exception will return the details for that exception. + * + * NOTE: this method is intend to be used in conjunction + * with the {@link ICAL.Event#iterator iterator} method. + * + * @param {Time} occurrence time occurrence + * @return {occurrenceDetails} Information about the occurrence + */ + }, { + key: "getOccurrenceDetails", + value: function getOccurrenceDetails(occurrence) { + var id = occurrence.toString(); + var utcId = occurrence.convertToZone(Timezone.utcTimezone).toString(); + var item; + var result = { + //XXX: Clone? + recurrenceId: occurrence + }; + if (id in this.exceptions) { + item = result.item = this.exceptions[id]; + result.startDate = item.startDate; + result.endDate = item.endDate; + result.item = item; + } else if (utcId in this.exceptions) { + item = this.exceptions[utcId]; + result.startDate = item.startDate; + result.endDate = item.endDate; + result.item = item; + } else { + // range exceptions (RANGE=THISANDFUTURE) have a + // lower priority then direct exceptions but + // must be accounted for first. Their item is + // always the first exception with the range prop. + var rangeExceptionId = this.findRangeException(occurrence); + var end; + if (rangeExceptionId) { + var exception = this.exceptions[rangeExceptionId]; + + // range exception must modify standard time + // by the difference (if any) in start/end times. + result.item = exception; + var startDiff = this._rangeExceptionCache[rangeExceptionId]; + if (!startDiff) { + var original = exception.recurrenceId.clone(); + var newStart = exception.startDate.clone(); + + // zones must be same otherwise subtract may be incorrect. + original.zone = newStart.zone; + startDiff = newStart.subtractDate(original); + this._rangeExceptionCache[rangeExceptionId] = startDiff; + } + var start = occurrence.clone(); + start.zone = exception.startDate.zone; + start.addDuration(startDiff); + end = start.clone(); + end.addDuration(exception.duration); + result.startDate = start; + result.endDate = end; + } else { + // no range exception standard expansion + end = occurrence.clone(); + end.addDuration(this.duration); + result.endDate = end; + result.startDate = occurrence; + result.item = this; + } + } + return result; + } + + /** + * Builds a recur expansion instance for a specific point in time (defaults + * to startDate). + * + * @param {Time=} startTime Starting point for expansion + * @return {RecurExpansion} Expansion object + */ + }, { + key: "iterator", + value: function iterator(startTime) { + return new RecurExpansion({ + component: this.component, + dtstart: startTime || this.startDate + }); + } + + /** + * Checks if the event is recurring + * + * @return {Boolean} True, if event is recurring + */ + }, { + key: "isRecurring", + value: function isRecurring() { + var comp = this.component; + return comp.hasProperty('rrule') || comp.hasProperty('rdate'); + } + + /** + * Checks if the event describes a recurrence exception. See + * {@tutorial terminology} for details. + * + * @return {Boolean} True, if the event describes a recurrence exception + */ + }, { + key: "isRecurrenceException", + value: function isRecurrenceException() { + return this.component.hasProperty('recurrence-id'); + } + + /** + * Returns the types of recurrences this event may have. + * + * Returned as an object with the following possible keys: + * + * - YEARLY + * - MONTHLY + * - WEEKLY + * - DAILY + * - MINUTELY + * - SECONDLY + * + * @return {Object.} + * Object of recurrence flags + */ + }, { + key: "getRecurrenceTypes", + value: function getRecurrenceTypes() { + var rules = this.component.getAllProperties('rrule'); + var i = 0; + var len = rules.length; + var result = Object.create(null); + for (; i < len; i++) { + var value = rules[i].getFirstValue(); + result[value.freq] = true; + } + return result; + } + + /** + * The uid of this event + * @type {String} + */ + }, { + key: "uid", + get: function get() { + return this._firstProp('uid'); + }, + set: function set(value) { + this._setProp('uid', value); + } + + /** + * The start date + * @type {Time} + */ + }, { + key: "startDate", + get: function get() { + return this._firstProp('dtstart'); + }, + set: function set(value) { + this._setTime('dtstart', value); + } + + /** + * The end date. This can be the result directly from the property, or the + * end date calculated from start date and duration. Setting the property + * will remove any duration properties. + * @type {Time} + */ + }, { + key: "endDate", + get: function get() { + var endDate = this._firstProp('dtend'); + if (!endDate) { + var duration = this._firstProp('duration'); + endDate = this.startDate.clone(); + if (duration) { + endDate.addDuration(duration); + } else if (endDate.isDate) { + endDate.day += 1; + } + } + return endDate; + }, + set: function set(value) { + if (this.component.hasProperty('duration')) { + this.component.removeProperty('duration'); + } + this._setTime('dtend', value); + } + + /** + * The duration. This can be the result directly from the property, or the + * duration calculated from start date and end date. Setting the property + * will remove any `dtend` properties. + * @type {Duration} + */ + }, { + key: "duration", + get: function get() { + var duration = this._firstProp('duration'); + if (!duration) { + return this.endDate.subtractDateTz(this.startDate); + } + return duration; + }, + set: function set(value) { + if (this.component.hasProperty('dtend')) { + this.component.removeProperty('dtend'); + } + this._setProp('duration', value); + } + + /** + * The location of the event. + * @type {String} + */ + }, { + key: "location", + get: function get() { + return this._firstProp('location'); + }, + set: function set(value) { + this._setProp('location', value); + } + + /** + * The attendees in the event + * @type {Property[]} + */ + }, { + key: "attendees", + get: function get() { + //XXX: This is way lame we should have a better + // data structure for this later. + return this.component.getAllProperties('attendee'); + } + + /** + * The event summary + * @type {String} + */ + }, { + key: "summary", + get: function get() { + return this._firstProp('summary'); + }, + set: function set(value) { + this._setProp('summary', value); + } + + /** + * The event description. + * @type {String} + */ + }, { + key: "description", + get: function get() { + return this._firstProp('description'); + }, + set: function set(value) { + this._setProp('description', value); + } + + /** + * The event color from [rfc7986](https://datatracker.ietf.org/doc/html/rfc7986) + * @type {String} + */ + }, { + key: "color", + get: function get() { + return this._firstProp('color'); + }, + set: function set(value) { + this._setProp('color', value); + } + + /** + * The organizer value as an uri. In most cases this is a mailto: uri, but + * it can also be something else, like urn:uuid:... + * @type {String} + */ + }, { + key: "organizer", + get: function get() { + return this._firstProp('organizer'); + }, + set: function set(value) { + this._setProp('organizer', value); + } + + /** + * The sequence value for this event. Used for scheduling + * see {@tutorial terminology}. + * @type {Number} + */ + }, { + key: "sequence", + get: function get() { + return this._firstProp('sequence'); + }, + set: function set(value) { + this._setProp('sequence', value); + } + + /** + * The recurrence id for this event. See {@tutorial terminology} for details. + * @type {Time} + */ + }, { + key: "recurrenceId", + get: function get() { + return this._firstProp('recurrence-id'); + }, + set: function set(value) { + this._setTime('recurrence-id', value); + } + + /** + * Set/update a time property's value. + * This will also update the TZID of the property. + * + * TODO: this method handles the case where we are switching + * from a known timezone to an implied timezone (one without TZID). + * This does _not_ handle the case of moving between a known + * (by TimezoneService) timezone to an unknown timezone... + * + * We will not add/remove/update the VTIMEZONE subcomponents + * leading to invalid ICAL data... + * @private + * @param {String} propName The property name + * @param {Time} time The time to set + */ + }, { + key: "_setTime", + value: function _setTime(propName, time) { + var prop = this.component.getFirstProperty(propName); + if (!prop) { + prop = new Property(propName); + this.component.addProperty(prop); + } + + // utc and local don't get a tzid + if (time.zone === Timezone.localTimezone || time.zone === Timezone.utcTimezone) { + // remove the tzid + prop.removeParameter('tzid'); + } else { + prop.setParameter('tzid', time.zone.tzid); + } + prop.setValue(time); + } + }, { + key: "_setProp", + value: function _setProp(name, value) { + this.component.updatePropertyWithValue(name, value); + } + }, { + key: "_firstProp", + value: function _firstProp(name) { + return this.component.getFirstPropertyValue(name); + } + + /** + * The string representation of this event. + * @return {String} + */ + }, { + key: "toString", + value: function toString() { + return this.component.toString(); + } + }]); + }(); + _defineProperty(Event, "THISANDFUTURE", 'THISANDFUTURE'); + function compareRangeException(a, b) { + if (a[0] > b[0]) return 1; + if (b[0] > a[0]) return -1; + return 0; + } + + /** + * The ComponentParser is used to process a String or jCal Object, + * firing callbacks for various found components, as well as completion. + * + * @example + * var options = { + * // when false no events will be emitted for type + * parseEvent: true, + * parseTimezone: true + * }; + * + * var parser = new ICAL.ComponentParser(options); + * + * parser.onevent(eventComponent) { + * //... + * } + * + * // ontimezone, etc... + * + * parser.oncomplete = function() { + * + * }; + * + * parser.process(stringOrComponent); + * + * @memberof ICAL + */ + var ComponentParser = /*#__PURE__*/function () { + /** + * Creates a new ICAL.ComponentParser instance. + * + * @param {Object=} options Component parser options + * @param {Boolean} options.parseEvent Whether events should be parsed + * @param {Boolean} options.parseTimezeone Whether timezones should be parsed + */ + function ComponentParser(options) { + _classCallCheck(this, ComponentParser); + /** + * When true, parse events + * + * @type {Boolean} + */ + _defineProperty(this, "parseEvent", true); + /** + * When true, parse timezones + * + * @type {Boolean} + */ + _defineProperty(this, "parseTimezone", true); + /* SAX like events here for reference */ + /** + * Fired when parsing is complete + * @callback + */ + _defineProperty(this, "oncomplete", /* c8 ignore next */function () {}); + /** + * Fired if an error occurs during parsing. + * + * @callback + * @param {Error} err details of error + */ + _defineProperty(this, "onerror", /* c8 ignore next */function (err) {}); + /** + * Fired when a top level component (VTIMEZONE) is found + * + * @callback + * @param {Timezone} component Timezone object + */ + _defineProperty(this, "ontimezone", /* c8 ignore next */function (component) {}); + /** + * Fired when a top level component (VEVENT) is found. + * + * @callback + * @param {Event} component Top level component + */ + _defineProperty(this, "onevent", /* c8 ignore next */function (component) {}); + if (typeof options === 'undefined') { + options = {}; + } + for (var _i = 0, _Object$entries = Object.entries(options); _i < _Object$entries.length; _i++) { + var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), + key = _Object$entries$_i[0], + value = _Object$entries$_i[1]; + this[key] = value; + } + } + return _createClass(ComponentParser, [{ + key: "process", + value: + /** + * Process a string or parse ical object. This function itself will return + * nothing but will start the parsing process. + * + * Events must be registered prior to calling this method. + * + * @param {Component|String|Object} ical The component to process, + * either in its final form, as a jCal Object, or string representation + */ + function process(ical) { + //TODO: this is sync now in the future we will have a incremental parser. + if (typeof ical === 'string') { + ical = parse(ical); + } + if (!(ical instanceof Component)) { + ical = new Component(ical); + } + var components = ical.getAllSubcomponents(); + var i = 0; + var len = components.length; + var component; + for (; i < len; i++) { + component = components[i]; + switch (component.name) { + case 'vtimezone': + if (this.parseTimezone) { + var tzid = component.getFirstPropertyValue('tzid'); + if (tzid) { + this.ontimezone(new Timezone({ + tzid: tzid, + component: component + })); + } + } + break; + case 'vevent': + if (this.parseEvent) { + this.onevent(new Event(component)); + } + break; + default: + continue; + } + } + + //XXX: ideally we should do a "nextTick" here + // so in all cases this is actually async. + this.oncomplete(); + } + }]); + }(); + + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + + + /** + * The main ICAL module. Provides access to everything else. + * + * @alias ICAL + * @namespace ICAL + * @property {ICAL.design} design + * @property {ICAL.helpers} helpers + */ + var ICALmodule = { + /** + * The number of characters before iCalendar line folding should occur + * @type {Number} + * @default 75 + */ + foldLength: 75, + debug: false, + /** + * The character(s) to be used for a newline. The default value is provided by + * rfc5545. + * @type {String} + * @default "\r\n" + */ + newLineChar: '\r\n', + Binary: Binary, + Component: Component, + ComponentParser: ComponentParser, + Duration: Duration, + Event: Event, + Period: Period, + Property: Property, + Recur: Recur, + RecurExpansion: RecurExpansion, + RecurIterator: RecurIterator, + Time: Time, + Timezone: Timezone, + TimezoneService: TimezoneService, + UtcOffset: UtcOffset, + VCardTime: VCardTime, + parse: parse, + stringify: stringify, + design: design, + helpers: helpers + }; + + return ICALmodule; + +})); diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/init.js b/hosts/vm/mediakiwi/FullCalendar/resources/init.js new file mode 100644 index 0000000..e3ee745 --- /dev/null +++ b/hosts/vm/mediakiwi/FullCalendar/resources/init.js @@ -0,0 +1,24 @@ +console.log("Youpi Calendar !"); + +mw.hook('wikipage.content').add(function() { + console.log("Content loaded ?"); + console.log(FullCalendar); + var elems = document.getElementsByClassName('mw-crans-fullcalendar'); + for (let el of elems) { + try { + const url = new URL(el.dataset.ics); + let calendar = new FullCalendar.Calendar(el, { + events: { + url: url.href, + format: 'ics' + } + }); + calendar.render(); + + } catch (e) { + console.error(e); + continue; + } + } + console.log(elems); +}); diff --git a/hosts/vm/mediakiwi/category-lockdown.patch b/hosts/vm/mediakiwi/category-lockdown.patch index e64c1bb..7816d48 100644 --- a/hosts/vm/mediakiwi/category-lockdown.patch +++ b/hosts/vm/mediakiwi/category-lockdown.patch @@ -2,14 +2,6 @@ diff --git a/CategoryLockdown.php b/CategoryLockdown.php index 3309c52..c4f4cb0 100644 --- a/CategoryLockdown.php +++ b/CategoryLockdown.php -@@ -1,6 +1,7 @@ - Date: Mon, 27 Jul 2026 09:53:03 +0200 Subject: [PATCH 60/77] Sync LDAP et fix ACLs --- hosts/vm/mediakiwi/category-lockdown.patch | 12 ++++++------ hosts/vm/mediakiwi/mediawiki.nix | 10 ++++++++++ secrets/mediakiwi/mediawiki-ldap.age | Bin 2410 -> 2800 bytes 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/hosts/vm/mediakiwi/category-lockdown.patch b/hosts/vm/mediakiwi/category-lockdown.patch index 7816d48..37de940 100644 --- a/hosts/vm/mediakiwi/category-lockdown.patch +++ b/hosts/vm/mediakiwi/category-lockdown.patch @@ -1,8 +1,8 @@ diff --git a/CategoryLockdown.php b/CategoryLockdown.php -index 3309c52..c4f4cb0 100644 +index 1e17ec7..8108615 100644 --- a/CategoryLockdown.php +++ b/CategoryLockdown.php -@@ -15,6 +16,8 @@ class CategoryLockdown { +@@ -16,6 +16,8 @@ class CategoryLockdown { */ public static function onGetUserPermissionsErrors( $title, $user, $action, &$result ) { global $wgCategoryLockdown; @@ -11,7 +11,7 @@ index 3309c52..c4f4cb0 100644 $explicitGroups = MediaWikiServices::getInstance()->getUserGroupManager()->getUserGroups( $user ); $implicitGroups = MediaWikiServices::getInstance()->getUserGroupManager()->getUserImplicitGroups( $user ); -@@ -25,6 +28,11 @@ class CategoryLockdown { +@@ -26,6 +28,11 @@ class CategoryLockdown { return; } @@ -23,7 +23,7 @@ index 3309c52..c4f4cb0 100644 $categories = array_keys( $title->getParentCategories() ); // Apply rules to the category page itself -@@ -32,16 +40,11 @@ class CategoryLockdown { +@@ -33,16 +40,11 @@ class CategoryLockdown { $categories[] = $title->getFullText(); } @@ -42,7 +42,7 @@ index 3309c52..c4f4cb0 100644 if ( !$permissions ) { continue; } -@@ -56,15 +59,44 @@ class CategoryLockdown { +@@ -57,15 +59,44 @@ class CategoryLockdown { $combinedGroups[] = $allowedGroup; } } @@ -72,7 +72,7 @@ index 3309c52..c4f4cb0 100644 + continue; # Skip if this group rule doesn’t match the user + } + -+ $requiredCat = $groupCategories[$action]; ++ $requiredCat = $groupCategories[ $action ] ?? null; + if ( $requiredCat == null ) { + continue; + } diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 9d4e376..6279eb9 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -146,6 +146,7 @@ in $wgGroupPermissions['*']['delete'] = true; # Création de comptes depuis LDAP et Note mais pas local $wgCategoryLockdownWhitelist = [ "Spécial:Connexion", + "Spécial:Déconnexion", "Spécial:Connexion/return", "Spécial:PluggableAuthLogin", "Spécial:Recherche", @@ -286,6 +287,15 @@ in # Pas de meilleure solution à ma connaissance pour suivre les releases. sha256 = "sha256-/9VISvpvhaVq/sAHE10INlTju7+PTirFO/6U68WBOgM="; }; + LDAPGroup = pkgs.fetchFromGitHub { + name = "LDAPGroup"; + owner = "wikimedia"; + repo = "mediawiki-extensions-LDAPGroups"; + tag = "3.1.0"; + # Le SHA doit être changé à chaque nouveau commit de traduction. + # Pas de meilleure solution à ma connaissance pour suivre les releases. + sha256 = "sha256-aW1nVechxy4CfyT953LfNgeUOXZGeIkV3KwPJJwlyug="; + }; WSOAuth = pkgs.applyPatches { src = pkgs.fetchFromGitHub { name = "WSOAuth"; diff --git a/secrets/mediakiwi/mediawiki-ldap.age b/secrets/mediakiwi/mediawiki-ldap.age index b28cc2b9c6e6835e10f87aa2f2ed5ece22d96e12..66b95e117a2d69004889298a89e9fad633e8eca3 100644 GIT binary patch literal 2800 zcmZXUiI>v^9>;l$%no(A_AT;2j&hDrXWBGv)2>`;liumww5eQankH?MUTM-!*;!9u z9AvpfZqPxILzhcX&J_?4fk99Nbdgi=i3p4ihX}LmzUO

|gMCzwht+{g4b@OJ}G; zeXf|wX6mv9MlA!1+R;i;WNYb!R<8$4N;zi4nUXS`Q3;$otHu6pz9+jWJJ`b0)7@{2!LV;#T_}vU1jXgQj55 zYG|Fc1WFpOPKSg7wu}WqCBkXPFsGm9fQq*qGb=!XlAKW)lLC1MD}dgd5zMM0syGJX zl;AJnE;VBE8r+yjIv!1t_QK&Hqoh)_H|JqkLQkMFL|LF<-e0Nsy}FQ&WKupRL)cI! zEV2+vC_-Gys1e9K=jCJZNQ~CvDU-*7ddXx3!018>LE|`U1Vo>T`;Ar*No4>fV`f+p z7Lc%DG^u!-1BmGY!6240vN2Bp<6MDKEaySh87mF@cswiufG(NL#Tb5|zAB2Bct!#1rmBLh=A9v?RdBbRcd|TM#Ui@%Vx{GOQ_;OB9ToX~7-hVitcE zE(CZ5XtS6z8Tn!U80NL8K{DX60btY!YC*G;H-xAP7?TZdD-#X53j`Y$Vri8FI6`#Ziv~5>k}sQ1$Z;!@%u@zc zDHDyeK#I30#CX0E2qY=i!PrP`B_GFgqT7nfET(2bP(rjoUdj^nP=+$YB|ak}Du4-O zteoDO(Lfj+R7hG0N;&l~YSNPR5uOs#8d~t@jAA+$Gl-I;qR=2qiV=~5B^h^;ZXkn2 z3o(<<c5jm{p86cTQ@^&*&Dv?%iIF<0F{V7Pa@P54-f^tT?!xD(&u5 zda?ouF(Y?j79NVHGZ43Q=JOXj8Y^ud3O+{mnKYocqDNwcAs2l^<@=3#@j zl2XkPfKOk9D}X9gio-dd$s#cT4;ZvkM0DHAD3(sxh%69gbqb#Ks@)~hX#%*27s2CM zgEbyWK*5BFhXX|`WEQwe1hncPgn5)kM=`hvD;ZmT4)N)V(YPJZu4x|OvP_oyX5G*Q4I$u&W(73@bT5NnI_n_cXwDE$=WS&;BtfLFp!1Lr64%!2U9l3xa*CK)Ph*HVjLN8A zi<%L)CQgVR!QspXZF)Ip3|8vdhh5aLCgRcC;sHC3!^!}Ut3iJ*LF;(b>F^2WqQz7Y zV-)U50jSI4aAJ0=(~uI4c8!AcF^VXk1$dmA#aKJjVhY~^dl@;DY-5FB$yYtR5~PzW}Q@VDBw72 zNH}AHkd<7SQj){MD%cqbL`p6a>ZUahdaQnV`><|})6a{0=wY_SFRk11hpNWl)em32``+sK znq0S?#qOltyi~h+=wS0-s?AB=yaOBu=Rsxp~v#$$0~-xRL$QW zE7)(p;%^jhNpmLJJNC~KYt9Vn-O?0aQx|m!7w%8(5o>9DY~XQ!4fI2wx0|`dAiQ;3 z>*3?j+`4Vj;z7?I9FbjlviI&C9j(Qs`?2<@VtM-~1!~NU&u6STJH74lW@qiVJ@Wg< z+9IlNHus)EfARLjKD)jXJ{@)xTVWYI>Gt^f=<}Cxb#>MEhZe)ow92aCbKmNRHIHq% z>D#wUTkAMJzoF){&u+Du_VK$;9;yCm{i3G6yY^0*y8Zq7&+%XO4!uhj)f-0c*BCq8 z5Z`(J+v3$0D%3b~+i2#6olj~nt~7KRr(Sh!=d8V}hpYQm?_YXo&ezR1c3?fZz8$Zu z=L&MUxhRjhG_d=D2V>6fp1u0ZOXI5A8#c|l zzW>sw7pnj0{d)_($3KmKRsEc8$eP{v%H40&omsD>&$GDtgPr3BEI%D`RxFp_eQx(a zMSIQub$=y~URwFZg7m?nuW97gQ>(&H6yNLp`X;OD95Vy!IIi%~$R*j;liu`@{~Yqh zV%Lx5)N~D1Jzjn9?EAy(CY1;M?&hU$uJ64tcJbq(*$orN^!bygy}`GkZO)Np5AN;t zIkR2$A743qGCXw0+Hr1l>D+v$txr8uTCoj&aIe?S3C=zIfTo|TF5H>${VC7OTAFJ) zbaBhn8#@**>firb?;R&$|A3wwYvA=ncCF^kJC#hYW%j{OgxBr+7<#fLuVNSk#Tu|K|Bp$kc|My2PHcUI*A#rx`(}ray4hVnHTov5Su>KHXO3=r{hA$>;j~xy{K|Z`kw3m0d&nQ!Bm% z(xWGBoiuUl39tBh=lHRzQ?Fee)zEmKjE|j%wvBc@uJt{!Y;>=q^G}aB7onb7nEznV lb<51L?L)4$&FS|`jqeoq?1EV)drRkqCx*`MvL$iW^Dib?PH_ML literal 2410 zcmZXU{nyik8HN$%hy|R0Q>O>4It7idY15`n6~%nCP1>eS(k4xNrYuR*q-olwX__=~ z2RY8YjyXQ2+wr&|_<=fgPClFthu~zw*_aM>z7B`Z4N-B5Lx(cO*Rdb`Vfq(b&wZc! zdL&I9Q@e`oTTZLjy{u`)SE!8nJR0o@` z&?6*IZblfyBXYT7IbZ9y)ErPNXR&t5u~^$HL{mn|G%?JRx!#~4#u|Kl6v!367y^+I zWOiD81ayien!s&EK9m1cwxHy&!@qS_INPA{r0KklIRB z@{w8@>JfFe1nMN{4(uQq5iHxSA_P)Rw4$JZ=XntiT)G~`du}V2DWP#A4YdFP4RQH0 z-)C{SB(;09%8N#G(s3j z=M))^THQXVL@-iS;uYB&Ax;+0@r4c*Ee{G^N^^L%+_VYb=ZXYa13lU{>NL+J{g5J6 z@_sFXGsidKl1v<0{w*X4^YMFXrI3yibWNTlhWs7<_>xH}3WU7#}O0iBiDd$ieb~r^z zmGX8=RTG60AYhUTnI4+N;-Ny6qB}HEOiOID>)729HXsDKowmA6R)BIz0jZ^Y*GzcI zS0xi^)Ir0;XutqYiWBM%F_H5#-G4F(HXpyBJo z7-kf8#bA0pK5pR7zzU!SV{=X!90~qcvcbC8Q`oRof?yHtleohqg+#ST%7AH=Gj6Sw z2w=kPRdLq@U~Ncv@sQsnn=(v_eG>6~&m-DRSBy%zk)6@1i5!$jlnpISbc#|O{c@8s zM+yQOZtA_7%HnvL6Dc;rf&o6r*tBhlBGep8$l$ou2&I<>mE$iUj^|T7Nap>1m2Q?& zty+fgm}sNjNJmnx)XSQ(+wPiy30PmrMrzfRiiTT_ILsEPLLy$n8i`t4SMn&HkR?tm z3s!}S%5*Q|1MQ$xRYDxv7eqa9ijWlK4b<*28Br{0L|#ZX3Sh>MH&E6}^|g`gg~rCl z!b9CIBinAtvKRjjcP4Al*-gD7UW}=ZzTh5uozVXS9?Q>SQ zZv5k#a_qp4yUi7Y)oYJ6C%^fxhZg;Lq)d6>?C@Dj7Dp#{`jZFy-+K1oFKhcBf8c^6 zTfcVoscOng>8bj89AUte&^rftG`g*gWf?z;8FtKNNJ*4Uz*+jeg{dFra~ zpY`6V;;nBlW47#J+Te`Or#@7n?!B#f{!I0SyWaWD<|nL)j~+R+_x5+RS6(}D>A!D1 z`uOt;G6$}n+h%|8;TgZeFJ2m+cc4G*_IXcC()-t6x%S}le>^{U{tkX!Co4X7#kuCR z_xG${QT@haQy!hQ`;yg%=A61?`9f-~`toVLkGGwA;oWzwI`Y>KmmEg-A3c2WrHS_@ zru~PXx$nLg$F6}snAou|nE%o})UtIePe1rsY4@Q^ix<3=m2zu0Tt4sS8*X~}&ZE1b zVPd|$_!a$)8H4?3SG#2~y?Z-$_Ttk&N!-70Y|Z$O!&ht{U$&3k^4R$5gC9)O zcfw0H3|B51zh;Jf#}ng`r6+8jO+5IfzA84!A}8JW3rxGAV6EYPe$vUeJvnvd7WSu- z_1uZ`W}o}o375Njs?Qufk2-$&%p>w_YW<;Er(N{oVm@+Naz?E5vjeN^zumi;czZJT zqWo}s*)zxdY&ySa%FQpG|LNGDR(*WZ;VZ?5u79-l$lKP&&6_6fgD;zT&vnO^C)S^H zdUw^<@9o|2yVKU8%Hmzu?0BW}mw)E}u;Op`F1U6fyCD1Q^~X+F@bT2;vvYH;r{3LZ iK7H|XQ(_yYe|O?=5&&l8U}(XdJI>s- Date: Mon, 27 Jul 2026 11:03:13 +0200 Subject: [PATCH 61/77] Improve wiki calendar --- .../themes/breezy/palettes/rose.css | 52 +++++++++++++++++-- .../fullcalendar7/themes/breezy/theme.css | 3 +- .../mediakiwi/FullCalendar/resources/init.js | 6 +++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/palettes/rose.css b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/palettes/rose.css index 7fb32bf..8a1014f 100644 --- a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/palettes/rose.css +++ b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/palettes/rose.css @@ -1,8 +1,8 @@ :root { /* primary */ - --fc-breezy-primary: #e11d48; - --fc-breezy-primary-over: #f43f5e; + --fc-breezy-primary: #AD1F1F; + --fc-breezy-primary-over: #AD1F1F; --fc-breezy-primary-foreground: #ffffff; /* secondary */ @@ -14,9 +14,9 @@ --fc-breezy-secondary-icon-over: var(--fc-breezy-muted-foreground); /* calendar content */ - --fc-breezy-event: #ec4899; + --fc-breezy-event: #1FAD95; --fc-breezy-event-contrast: #fff; - --fc-breezy-background-event: #f97316; + --fc-breezy-background-event: #1F95AD; --fc-breezy-highlight: #ef444414; --fc-breezy-now: #ef4444; @@ -48,7 +48,49 @@ } @media not print { - [data-color-scheme=dark] { + :root.skin-theme-clientpref-night { + /* secondary */ + --fc-breezy-secondary: var(--fc-breezy-muted); + --fc-breezy-secondary-over: var(--fc-breezy-strong); + --fc-breezy-secondary-border: var(--fc-breezy-muted-border); + --fc-breezy-secondary-foreground: var(--fc-breezy-strong-foreground); + --fc-breezy-secondary-icon: var(--fc-breezy-muted-foreground); + --fc-breezy-secondary-icon-over: var(--fc-breezy-foreground); + + /* calendar content */ + --fc-breezy-highlight: #f8717114; + + /* controls */ + --fc-breezy-selected: var(--fc-breezy-muted); + + /* popovers */ + --fc-breezy-popover: #1f2937; + --fc-breezy-popover-border: var(--fc-breezy-border); + + /* neutral backgrounds */ + --fc-breezy-background: #111827; + --fc-breezy-faint: #ffffff08; + --fc-breezy-muted: #ffffff12; + --fc-breezy-strong: #ffffff1F; + --fc-breezy-stronger: #ffffff29; + --fc-breezy-strongest: #ffffff33; + + /* neutral foregrounds */ + --fc-breezy-foreground: #d1d5db; + --fc-breezy-muted-foreground: #9ca3af; + --fc-breezy-faint-foreground: #6b7280; + --fc-breezy-strong-foreground: #ffffff; + + /* neutral borders */ + --fc-breezy-border: #ffffff1A; + --fc-breezy-muted-border: #ffffff0D; + --fc-breezy-strong-border: #ffffff26; + } +} + +@media not print and (prefers-color-scheme:dark){ + /* Copie du thème night */ + :root.skin-theme-clientpref-os { /* secondary */ --fc-breezy-secondary: var(--fc-breezy-muted); --fc-breezy-secondary-over: var(--fc-breezy-strong); diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/theme.css b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/theme.css index 493bc97..386adea 100644 --- a/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/theme.css +++ b/hosts/vm/mediakiwi/FullCalendar/resources/fullcalendar7/themes/breezy/theme.css @@ -803,7 +803,8 @@ } .fc-breezy-TZ4 { - white-space: nowrap + /* PATCH Crans : pour wrap le texte et pouvoir le lire sur mobile */ + /* white-space: nowrap */ } .fc-breezy-jm6 { diff --git a/hosts/vm/mediakiwi/FullCalendar/resources/init.js b/hosts/vm/mediakiwi/FullCalendar/resources/init.js index e3ee745..a498e27 100644 --- a/hosts/vm/mediakiwi/FullCalendar/resources/init.js +++ b/hosts/vm/mediakiwi/FullCalendar/resources/init.js @@ -8,6 +8,12 @@ mw.hook('wikipage.content').add(function() { try { const url = new URL(el.dataset.ics); let calendar = new FullCalendar.Calendar(el, { + headerToolbar: { + start: 'prev,today,next', + center: 'title', + end: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek,multiMonthYear', + }, + locale: 'fr', events: { url: url.href, format: 'ics' From 18cf9148ed8f23ce136c315d11636e5e87958361 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Mon, 27 Jul 2026 15:54:10 +0200 Subject: [PATCH 62/77] =?UTF-8?q?end=20of=20debug=E2=80=AF!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hosts/vm/mediakiwi/mediawiki.nix | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 6279eb9..9e9d87a 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -96,7 +96,7 @@ in ]; $wgPasswordSender = 'root@crans.org'; $wgEmergencyContact = 'contact@crans.org'; - $wgNoReplyAddress = 'root@crans.org'; + $wgNoReplyAddress = 'contact@crans.org'; # On reçoit les réponses aux mails $wgEnableUserEmail = false; $wgAllowHTMLEmail = true; @@ -110,7 +110,7 @@ in $wgUsersNotifiedOnAllChanges = []; # Auth - $wgPluggableAuth_EnableLocalLogin = true; + $wgPluggableAuth_EnableLocalLogin = false; $LDAPAuthentication2AllowLocalLogin = true; $LDAPProviderDomainConfigs = "${config.age.secrets.mediawiki-ldap.path}"; $wgOAuthCustomAuthProviders = [ @@ -178,13 +178,6 @@ in $wgNamespacesWithSubpages[NS_MAIN] = true; $wgNamespacesWithSubpages[NS_ARCHIVE] = true; $wgVisualEditorAvailableNamespaces[NS_ARCHIVE] = true; - - # Debug - $wgShowExceptionDetails = true; - $wgDebugToolbar = true; - $wgDebugLogFile = "/var/log/mediawiki/debug.log"; - # $wgShowDebug = true; - # $wgDevelopmentWarnings = true; ''; skins = { From 280e0ee8a05280bfb496f6451c5b8d0df8af68f0 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Tue, 28 Jul 2026 13:50:34 +0200 Subject: [PATCH 63/77] last smalls fixes --- hosts/vm/mediakiwi/mediawiki.nix | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 9e9d87a..dab7ce4 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -94,7 +94,7 @@ in 'port' => 25, 'auth' => false, ]; - $wgPasswordSender = 'root@crans.org'; + $wgPasswordSender = 'wiki@crans.org'; # C’est un alias de root (pas de réponse) $wgEmergencyContact = 'contact@crans.org'; $wgNoReplyAddress = 'contact@crans.org'; # On reçoit les réponses aux mails $wgEnableUserEmail = false; @@ -107,10 +107,10 @@ in $wgEnotifMinorEdits = true; $wgEnotifWatchlist = true; # Peut-être utilisé pour les Wikistes - $wgUsersNotifiedOnAllChanges = []; + $wgUsersNotifiedOnAllChanges = [ 'Ninja_wiki_notifs' ]; # Auth - $wgPluggableAuth_EnableLocalLogin = false; + $wgPluggableAuth_EnableLocalLogin = true; $LDAPAuthentication2AllowLocalLogin = true; $LDAPProviderDomainConfigs = "${config.age.secrets.mediawiki-ldap.path}"; $wgOAuthCustomAuthProviders = [ @@ -178,6 +178,7 @@ in $wgNamespacesWithSubpages[NS_MAIN] = true; $wgNamespacesWithSubpages[NS_ARCHIVE] = true; $wgVisualEditorAvailableNamespaces[NS_ARCHIVE] = true; + $wgContentNamespaces[NS_ARCHIVE] = true; # Pour les Popups ''; skins = { From dc9a0cf7ef5907c85d7cc4d41b26b2e8b0114905 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Tue, 28 Jul 2026 15:15:40 +0200 Subject: [PATCH 64/77] search --- hosts/vm/mediakiwi/mediawiki.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index dab7ce4..cb255e9 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -179,6 +179,8 @@ in $wgNamespacesWithSubpages[NS_ARCHIVE] = true; $wgVisualEditorAvailableNamespaces[NS_ARCHIVE] = true; $wgContentNamespaces[NS_ARCHIVE] = true; # Pour les Popups + $wgNamespacesToBeSearchedDefault[NS_ARCHIVE] = true; # Temporairement pour la migration + $wgNamespacesToBeSearchedDefault[NS_USER] = true; ''; skins = { From 5d9c7552c8428adae03c119651e7b716ecb35f13 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sat, 1 Aug 2026 21:30:06 +0200 Subject: [PATCH 65/77] Fix popus and update skin --- hosts/vm/mediakiwi/mediawiki.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index cb255e9..bd51594 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -178,7 +178,7 @@ in $wgNamespacesWithSubpages[NS_MAIN] = true; $wgNamespacesWithSubpages[NS_ARCHIVE] = true; $wgVisualEditorAvailableNamespaces[NS_ARCHIVE] = true; - $wgContentNamespaces[NS_ARCHIVE] = true; # Pour les Popups + $wgContentNamespaces[] = NS_ARCHIVE; # Pour les Popups $wgNamespacesToBeSearchedDefault[NS_ARCHIVE] = true; # Temporairement pour la migration $wgNamespacesToBeSearchedDefault[NS_USER] = true; ''; @@ -188,8 +188,8 @@ in name = "Citizen"; owner = "StarCitizenTools"; repo = "mediawiki-skins-Citizen"; - tag = "v3.17.0"; - sha256 = "sha256-IPwfI2ldzis3Zni5QSsrSfi0Qt6+xjOupPJk+j7P6aM="; + tag = "v3.19.0"; + sha256 = "sha256-iQS0lyvOYH45gI6fMKYV4ERK9JO35J8ktLNrQi6UsH0="; }; }; From ad343789a090fc258b4bc67aaab1680d7aaeec89 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sat, 1 Aug 2026 22:59:56 +0200 Subject: [PATCH 66/77] mediawiki: jobs de fond --- hosts/vm/mediakiwi/mediawiki.nix | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index bd51594..dde7f66 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -52,6 +52,41 @@ in services.syslogd.enable = true; + # Tâches lourdes à gérer au fond https://www.mediawiki.org/wiki/Manual:Job_queue + systemd.services.mediawiki-jobs = + let + # Hack pour retrouver la conf + conf = config.services.phpfpm.pools.mediawiki.phpEnv.MEDIAWIKI_CONFIG; + php = "${config.services.mediawiki.phpPackage}/bin/php"; + maintenanceScript = "${config.services.mediawiki.finalPackage}/share/mediawiki/maintenance/run.php"; + runJobs = "MEDIAWIKI_CONFIG='${conf}' ${php} ${maintenanceScript} runJobs --maxtime=3600"; + in + { + description = "Tâches lourdes de Mediawiki"; + wantedBy = [ "multi-user.target" ]; + after = [ "phpfpm-mediawiki.service" ]; + path = [ pkgs.mediawiki ]; + # Inspiré de https://www.mediawiki.org/wiki/Manual:Job_queue + script = '' + while true; do + ${runJobs} --type="enotifNotify" + # --wait pour ne pas boucler pour rien + ${runJobs} --wait --maxjobs=20 + # Au bout de 20 jobs on attend pour éviter une surcharge du serveur + sleep 10 + done + ''; + + serviceConfig = { + Restart = "always"; + RestartSec = "60s"; + User = "mediawiki"; + Nice = 10; + ProtectSystem = "full"; + OOMScoreAdjust = 200; + }; + }; + services.mediawiki = { enable = true; @@ -72,6 +107,7 @@ in # Server settings $wgFavicon = 'https://www.crans.org/images/favicon.ico'; $wgLogo = 'https://www.crans.org/images/crans.svg'; + $wgJobRunRate = 0; # On fait les jobs manuellement dans un systemd. # Files and Uploads $wgMaxUploadSize = 512 * 1024 * 1024; From 22c30eaa968cad88e0a7991822c1893ebef4e6d7 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sun, 2 Aug 2026 17:36:55 +0200 Subject: [PATCH 67/77] fix popup pages perso --- hosts/vm/mediakiwi/mediawiki.nix | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index dde7f66..561fb72 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -72,8 +72,9 @@ in ${runJobs} --type="enotifNotify" # --wait pour ne pas boucler pour rien ${runJobs} --wait --maxjobs=20 - # Au bout de 20 jobs on attend pour éviter une surcharge du serveur - sleep 10 + # Au bout de 20 jobs on attend pour éviter de surcharger le serveur + # avec des tâches de fond + sleep 5 done ''; @@ -108,6 +109,7 @@ in $wgFavicon = 'https://www.crans.org/images/favicon.ico'; $wgLogo = 'https://www.crans.org/images/crans.svg'; $wgJobRunRate = 0; # On fait les jobs manuellement dans un systemd. + $wgEnableEditRecovery = true; # Pour pouvoir récuppérer des changements non sauvegardées (1 mois par défaut en local) # Files and Uploads $wgMaxUploadSize = 512 * 1024 * 1024; @@ -214,8 +216,11 @@ in $wgNamespacesWithSubpages[NS_MAIN] = true; $wgNamespacesWithSubpages[NS_ARCHIVE] = true; $wgVisualEditorAvailableNamespaces[NS_ARCHIVE] = true; - $wgContentNamespaces[] = NS_ARCHIVE; # Pour les Popups - $wgNamespacesToBeSearchedDefault[NS_ARCHIVE] = true; # Temporairement pour la migration + # Pour les Popups + $wgContentNamespaces[] = NS_ARCHIVE; + $wgContentNamespaces[] = NS_USER; + # Pour la recherche + $wgNamespacesToBeSearchedDefault[NS_ARCHIVE] = true; # Temporairement pour la migration ? $wgNamespacesToBeSearchedDefault[NS_USER] = true; ''; From 74f1f1413e1cdbbc717b5be692fadfa98b559abc Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Wed, 12 Aug 2026 20:09:29 +0200 Subject: [PATCH 68/77] wiki: DB sur tealc --- hosts/vm/mediakiwi/mediawiki.nix | 14 +++++++++++ secrets/mediakiwi/mediawiki-db-pass.age | 33 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 secrets/mediakiwi/mediawiki-db-pass.age diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 561fb72..61dc1f4 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -27,6 +27,11 @@ in owner = "mediawiki"; }; + age.secrets.mediawiki-db = { + file = ../../../secrets/mediakiwi/mediawiki-db-pass.age; + owner = "mediawiki"; + }; + environment.systemPackages = with pkgs; [ imagemagick @@ -99,6 +104,15 @@ in passwordFile = config.age.secrets.mediawiki-admin-passwd.path; + database = { + createLocally = false; + host = "tealc.adm.crans.org"; + name = "mediawiki"; + user = "mediawiki"; + passwordFile = config.age.secrets.mediawiki-db; + type = "postgres"; + }; + extraConfig = '' # TODO remove log error in test error_reporting( -1 ); diff --git a/secrets/mediakiwi/mediawiki-db-pass.age b/secrets/mediakiwi/mediawiki-db-pass.age new file mode 100644 index 0000000..048e90f --- /dev/null +++ b/secrets/mediakiwi/mediawiki-db-pass.age @@ -0,0 +1,33 @@ +age-encryption.org/v1 +-> ssh-ed25519 vZ8Vgw 8qDmlfKzDjDJr49/xY0KW9qOsjw7tZnWr+i0uBARhjI +DQw661vgTzjEBN5jLE56hWu2mjK67AK5MlfE695dPcE +-> piv-p256 ewCc3w AvqksWKVJbd7CnRIjs/yKfjhWVBbPO82XJjYVmh/kasN +bh6ofS1lUU++xKVdzQOe3LAs/Rwa66TlitogPHONgAk +-> piv-p256 6CL/Pw A/4k87YHJQdTnlMAeQiCDNYawpLXHKc2dHwnVle2dYFR +PVCCARxrMvHqI0GyjS2k78gwigYkj8SttDCCjc3IJDc +-> ssh-ed25519 eOAUSg Y7bTQMzWA3tR7k44rsJBZBiks5Okx5JuZMrkyWVpRGg +qVnVRkFYgozvVt0D9rwXi58ZOPuYF8Zfby0q6AHG/MQ +-> ssh-rsa REaZBA +NyxP0jfq3Kmg2JWZDCSfcdOhWlJkJe46lTIfSpSaeMTmSdiBLav8M/R9Kjsp5FtH +vG/880koXjPKO5FyfQyvkq/WEJBY9babY6p8fPmRz2lSKyrYDlDVdE/30KoU/0GE +t/BkPeuCkpE9zKSJWzNKfVRbwMJNRyW7jMHBAVsnSoJfHa0WXrsPD+osN5o3zrwI +37EVokVs8cyDRoEhE7qox6iMiB0IcjDK0OZ2gNbJYVFE2VttnjT91zsNKKlZk/H9 +uPs1BGL2ynsi+CnL9IQX1WlyZUt6r2XLJg54LQhSSNt7sGvjMBXMLdF3I2kNwC4/ +uFmlFNCr/CeCaDpLRIERfm9Y05lsDfB/5lmREgX2G9LMPbCBHDeFDi077cO074oF +6c9NEgC3b8RaW3kZ02fPG84+fTv6UStjb4WDu15AmqxoVYoXiO2dCCvXHU26VSlE +3Cp52woqxt0b6wZanHfPu5+pT0cmBn3KD5TIZwEjB6QKS1aCBHZpfhABDuFu+EPO + +-> ssh-ed25519 J/iReg YE2Ur1V8M0+dSB++jEv5UzOLwLrhb+NYdedUKNrfe2M +w9vGtwGUoYZPuU8cr1EglfRwexdx+aLA5++isPq478Y +-> ssh-ed25519 GNhSGw OVh/yHW2aIAKB3xPBlNW7xjKKSO0LjtjAzDCoNxZcyc +0PBtbwz9uTMJF+cDWkRF2m+2Sapnwk3LfDn5izM0MWI +-> ssh-ed25519 eXMAtA wku3bHXp5BEozX9pOCh0sLuH+nM/dUKU/Seel733RSk +O8eWnryyVi5lzCg819NYHUcu4rqF12TmE8vh/PnBw4M +-> ssh-ed25519 5hXocQ ansyFMSEbq4MWEmCc14EWUdpzjrfreRFdapAKIBUTn0 +41y5NpflbdXvsMPDSR+zNR7+Q6PzOWzI1An6VbN/1yI +-> ssh-ed25519 bRHVVA cb/IWLLb6r/6GdE2BcCrfeOYOeHZiU3lig9qaSxNvjE +Wz/Qzrqs+ZcnjI9uPK8AR8pjqEWcALn4MWH+ZS1kF2I +-> ssh-ed25519 HgW9eA ynUoOk8XEQIQxW40iZqn6Brzuw1+T7p1zW8yBiDy9Ak +6Udtd/dEPZ43B79K+oLJj3Ek/n3lMniz5XPB04Q5aus +--- Dz0Xb2xUxZcKYSVmILBu1xlgKzi7dvMnIZ6ANpUxqzY +vפ2>"l= "_U|a=|0 ֑HТ!F޵Y9ǟ& \ No newline at end of file From 402d8e37761deeb184008901a0feea8419a6a96c Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Wed, 12 Aug 2026 20:11:29 +0200 Subject: [PATCH 69/77] wiki: fix categories subpages --- hosts/vm/mediakiwi/mediawiki.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 61dc1f4..20b548f 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -227,8 +227,10 @@ in NS_ARCHIVE => "Archive", NS_ARCHIVE_TALK => "Discussion_archive", ]; + # Sous pages $wgNamespacesWithSubpages[NS_MAIN] = true; $wgNamespacesWithSubpages[NS_ARCHIVE] = true; + $wgNamespacesWithSubpages[NS_CATEGORY] = true; $wgVisualEditorAvailableNamespaces[NS_ARCHIVE] = true; # Pour les Popups $wgContentNamespaces[] = NS_ARCHIVE; From 03e91a8ded2cd85942228fce959241298a4b7c1d Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Thu, 13 Aug 2026 08:50:57 +0200 Subject: [PATCH 70/77] fix age path --- hosts/vm/mediakiwi/mediawiki.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 20b548f..0a90d1a 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -109,7 +109,7 @@ in host = "tealc.adm.crans.org"; name = "mediawiki"; user = "mediawiki"; - passwordFile = config.age.secrets.mediawiki-db; + passwordFile = config.age.secrets.mediawiki-db.path; type = "postgres"; }; From ece2ea7fa66d9053cb1544e6730bbe0333c7c950 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Thu, 13 Aug 2026 09:27:55 +0200 Subject: [PATCH 71/77] wiki: hardware --- hosts/vm/mediakiwi/hardware-configuration.nix | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/hosts/vm/mediakiwi/hardware-configuration.nix b/hosts/vm/mediakiwi/hardware-configuration.nix index de32983..4397354 100644 --- a/hosts/vm/mediakiwi/hardware-configuration.nix +++ b/hosts/vm/mediakiwi/hardware-configuration.nix @@ -14,18 +14,11 @@ boot.extraModulePackages = [ ]; fileSystems."/" = - { device = "/dev/disk/by-uuid/74148438-bd6e-4c19-a41c-d20c907f1fc1"; + { device = "/dev/disk/by-uuid/45e1f37b-5bf2-47fc-86ef-e79b062c2b3c"; fsType = "ext4"; }; swapDevices = [ ]; - # Enables DHCP on each ethernet and wireless interface. In case of scripted networking - # (the default) this is the recommended approach. When using systemd-networkd it's - # still possible to use this option, but it's recommended to use it in conjunction - # with explicit per-interface declarations with `networking.interfaces..useDHCP`. - networking.useDHCP = lib.mkDefault true; - # networking.interfaces.ens18.useDHCP = lib.mkDefault true; - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; } From e7fd9088e12b68336d4755d8cd3d79ec08b1f290 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Thu, 13 Aug 2026 18:13:58 +0200 Subject: [PATCH 72/77] wiki: lock dependencies --- hosts/vm/mediakiwi/mediawiki.nix | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 0a90d1a..a578d46 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -1,14 +1,10 @@ { - lib, pkgs, config, ... }: let - version = pkgs.mediawiki.version; - major = lib.versions.major version; - minor = lib.versions.minor version; phpExtensions = config.services.mediawiki.phpPackage.extensions; in { @@ -285,10 +281,10 @@ in name = "CategoryLockdown"; owner = "wikimedia"; repo = "mediawiki-extensions-CategoryLockdown"; - rev = "REL" + major + "_" + minor; + rev = "d29e734c87697590af80f91d0cdbb4275bc3f072"; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-KrqGAvw0/cJdPijF8aOU58l8ZlCunzkGYjDjwyAQpzI="; + sha256 = "sha256-iYRzCzWu3hndPoMIkFZOaYJkbVoAnVv/ZMZTSAcQdKo="; }; patches = [ # Cette extension s’occupe des du contrôle d’accès du Wiki @@ -306,10 +302,10 @@ in name = "Popups"; owner = "wikimedia"; repo = "mediawiki-extensions-Popups"; - rev = "REL" + major + "_" + minor; + rev = "cfe78f2c07fa5cf6edcb109eadd8b4b01df58d92"; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-4lsh6b2Xyg7B6W7ZKkH3BhdzFpWrU5gDDZRwXAfcjIw="; + sha256 = "sha256-lE64Tl0KmUoNHnN2bkNYvhexz5s1wMQ1bDvWSoWTdZU="; }; # Auth @@ -317,10 +313,10 @@ in name = "PluggableAuth"; owner = "wikimedia"; repo = "mediawiki-extensions-PluggableAuth"; - rev = "REL" + major + "_" + minor; + rev = "3b9428bd8832aeb8ef8ff1ea61b7a4afca7f0ecb"; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-ZGw0Wgz0Sg04YDcOzkOGywmfQ6s6Ex17QbjmUDO1D8c="; + sha256 = "sha256-9PUf7KC0oz4XeN/m/5KbefNpsgRwV2NswMQZl7a/+I0="; }; LDAPProvider = pkgs.fetchFromGitHub { name = "LDAPProvider"; @@ -354,10 +350,10 @@ in name = "WSOAuth"; owner = "wikimedia"; repo = "mediawiki-extensions-WSOAuth"; - rev = "REL" + major + "_" + minor; + rev = "4b8a12d26196b323be5da93480b6a75fda25b6d0"; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-04VCOKfrgrijrv9CdjXeaExmAsxIEWcWMz8W2KVutGI="; + sha256 = "sha256-W2SL14U5nVUcf/aTkWuVk9+vQGd3RCtuR3bAhBMN5iY="; }; patches = [ "${./WSOAuth.patch}" ]; }; From 0c07e206fb4ccaf7b5eec0bc5731454a5f910346 Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Thu, 13 Aug 2026 18:16:07 +0200 Subject: [PATCH 73/77] wiki: activation restic --- hosts/vm/mediakiwi/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/vm/mediakiwi/default.nix b/hosts/vm/mediakiwi/default.nix index bba9007..e4a80a0 100644 --- a/hosts/vm/mediakiwi/default.nix +++ b/hosts/vm/mediakiwi/default.nix @@ -21,7 +21,7 @@ }; # Enable when deploying the real mediakiwi - resticClient.enable = false; + resticClient.when = "06:47"; }; system.stateVersion = "25.05"; From 396400466591610a92dc15d7a14b3ba8fe04560b Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Thu, 13 Aug 2026 18:46:43 +0200 Subject: [PATCH 74/77] =?UTF-8?q?wiki:=20rekey=20(manque=20restic=20que=20?= =?UTF-8?q?je=20n=E2=80=99ai=20pas=20:/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (et déso @Lyes pour le conflit de merge :/) --- secrets.nix | 2 +- secrets/common/root.age | Bin 3164 -> 3164 bytes secrets/mediakiwi/mediawiki-admin-passwd.age | 60 +++++++++---------- secrets/mediakiwi/mediawiki-db-pass.age | 60 +++++++++---------- secrets/mediakiwi/mediawiki-ldap.age | Bin 2800 -> 2800 bytes secrets/mediakiwi/mediawiki-oauth.age | Bin 2000 -> 2000 bytes 6 files changed, 61 insertions(+), 61 deletions(-) diff --git a/secrets.nix b/secrets.nix index e7a004e..be3f3de 100644 --- a/secrets.nix +++ b/secrets.nix @@ -40,7 +40,7 @@ let collabora = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFa2D9fREtO2r2oIx6q9JAKFUHtxGbgEPMjkx09DQSU8 root@collabora"; jitsi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB6jVMIZ5y2oXX9HOkw7r5UUjw95MlFaFuu7FnEC0Q8z root@jitsi"; livre = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEVfKNokHG6ig32hhQxTep+fKFmKahlDClPrX/dP4/gb root@livre"; - mediakiwi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAiCZU+gdUt2jOxR0niVFsNzw0LIleYvwNhMFIANR5YE root@mediakiwi"; + mediakiwi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFfcKtim7Aj+strHdg847D7KCCdoOAuasxx/2yi2TY1n root@mediakiwi"; neo = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMGfSvxqC2PJYRrxJaivVDujwlwCZ6AwH8hOSA9ktZ1V root@neo"; nextcloud = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMgSP9UmuJw8Bi2ML07WHsWvxN8akkc9XZxXyOgdjXkq root@nextcloud"; periodique = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHTdfSIL3AWIv0mjRDam6E/qsjoqwJ8QSm1Cb0xqs1s1 root@periodique"; diff --git a/secrets/common/root.age b/secrets/common/root.age index 2df9ddc8f1d1018e9db898ca022f000322ca3996..2f641d73745d6034e7e3e169067f95e633510437 100644 GIT binary patch literal 3164 zcmZXWx$8XtUB{IOfwi&FLJeVKAkFa2+{Y&PgSqdW`&w*fXJ+Rq#1!c) zETg4A6$w$aut;ZPNJ2;-Gzf||UMWoCCg6|o`Thg%2d~%r^*rJ{dzW>|xD9jJcc1OY z99-bTyAO`jRJ_Yl1jpgi5vlRBkskvOfO|_>l1Hm-jJD0l5N`GArzqs8LYwP##VZkS%~sZ)vhJTaWd4Ma{(&niSbF zs~)F8_0V2dA3B**s<0uDZ!F=ZW?8zf4?_(#7vnzd0X=*6qZx(?GFA3rtG2n1oG0ox zLGXfr3-Y(L(oM;XJ~Eza&@IT}c+_R^o(w!S>QAaI`CF~_rUJ?uJo^9nkTfYuo4bEv>+(z@m?;OreHWS zz$MT1xLncV(h$;cJ}YJ-?I@{7T(v#GDaymOvaNN*DH)T`5G+tu(MzS zQd{=%j!HW3cT7*oHJ%q$ph}xB8$PK4feGDc9XQ}3pe^$59+o8 z=<;+TDcv1p9=GeQWmWouMcYGr=+;@g!YrSL{3sD=fYO?3Ihqma83){o6gn5xl0ENi4V|M1?fXmS&~AR9 zqA}XaJ`QuB5~1*s-6m@B2W(FsOU_Tm za)sW~L^kn5`X~cXoEGWFuc2I4`Icto%_ zt1I12-p`kZAzhtn=Fi$4zWaU!al_UZ-dmbR%MwfbX`&BdeJ9MWKM454K z>)6+ZSMl++-d`_u^TTGrC#LQiSVhHRbYOm0&cyt9W}J5b>JYgTWwPW~${5>r>oIRx zU-v%BB9QknT6KaLi=|o9Z)xuRX=v*alo8hy3vi2B?Fo_KS~~c)Es2lB)>1V+i2;ZX z@8-Xz{qhpS$6cnbpzb;`tfyE;A=OZ-o0L%F;g5S%z>E1RI} zkp_UrrJvOEO$yU?bz6wvo^!fTNp#7Y_$t{_Jw^70Rg!QJ#fM;@Qv)WJbHchg1kkm6 zOQ5WaR#zaRR#?SJ2yAfrsoFkb5v&H9FV?=jE}J)`oHD^#IK9{W%%@6RVgR9tyN`Cz)7 z64T%xInc0G0%Vk_P^ymnvrFKKS zXgZa)V$0omZ1D<~+8o*Zk!%k+eO}XXnf#GjtS#fzkgYHGM<~i0=QincqJc_D*b}?j zzJ0+MTH$dz(C$8CmpOtL2LS^sp1R|QwAG{0L?cTrsDvsaE!evi#%}b-BCi$XP4S9%xK-ube5#oD@T|8Yz&Y% zFJ=lMW`tos%gwh zFn!q;tSk=P4fn&4b*PP$$lQ?Qp0_F*)3vpaaFQ=jzz4L54}d5`m#fK7b+NA)MC?i9 zTg(YQbb;Dk0|sQZOT;A_As5gV*>jpHNV^QzxbDpL^(Fj{#oqezOn)}UB8qR8q3=lW z@D6VeQ2|Rk%a4Ae)aljNLfxxHD>=#1E;e?Ko?aY3-!qB1h3)3Tcms65_Fl1&<> z8p)JLHEhs!kh2X7Xab6IaO31!lrNd}d$mVA7mm0dtF)zO(KmVCsk7D27=gHVlte0Pbi*>$Y#Hd)BRVNIc$#2x5#etCXgn{D!Twk*`? znLdWhoXmV#k>;9`H|`#UK~Oy?L!f=Zx2uLW_ue}t_%-kjl*x9e8^cR_WA}Uw%g((= zYc`*Vi(+_wIx3x!++%8NKN8vh7f8Ga`@}l3)+x8e`soSajeE=wbFGLA=pp{1?|#3J z{d5-GDUD@hJs^+}0V#Dly<%^VG&AOV7~eg*we97Spw9Trr!&+t1bCHecS*D z04N{tVL7Vn9+^fkSaLJO8!B(3B2IYM)i^olH|jv*yAZ{==d?DXheiu4Zu^t~T_>^Hyv2l=o64)ft}efE#xPksJR+;9BZ zH@{+h{jYxRtMsS8^1;9V&-^b5I()ehy|4YzKRdtrr(d3`Um$+tZ@&8v-}%JErMhzWqyoxcuFZ rfAQb0|M}s@|26kt!{7eOmmZ({`}23dntnWpKl91I{PPdKne_h$Wz7=( literal 3164 zcmZXW%kTVpUB)47Om_zqLQXJ4+}Zr>Oz)M5^fuFWdcU{3-rMQ)HoZ<`;zmNEyA#6( zLI4dILr9dcfCSh83vr=w<3d@OqefSREgQa?92Vc-AMnZR^Sm$5`-zI|P1YulXFpY4 z`v={lfR+>R=Cg-!EZ<})jA1A00N0qR6$j+91-eo4xgDP*b7GB4irt_-vqTcfddS;Q zZLdo`DVYEeNbMQ=D0FcsPvt?xxDH_gC;N$T!Fw;QBLyv(e86+C(^3=nen%0B|? zXhe%o)`og2t>e7XHT}RlMH=dbR_IzA0*kYh;mFbAYg&noobLuQVvTNINnp@MPB6tWMS=LD6;z;D9wxH+6XAgw$rtD(F69Fmu?t z8v2^Hgrr-nhZ>}zovU^&rGuU}g>GFt&7fRJ^9qx_i#) z17G8oy4y|L((={7qh5V-O^?7e3hg1p3$j?ZT7Vd{tpILzcS1tdZZULUuKt440eW<# z-UG7$tF1CVEfCm}^(64GY3E6#`r<&xbBg7$Al?zOmrAg4sy$k-Y-8GCccnT!Cew7t zkl={|5LJM9Gl11`v?(DjS81+Xq`(D?)Z02j7|NW^@uuYcTa_&Bdn3_@OH(*8M~Yh3 zkOh+mGxc=LmAzH{q=JSld;G0@T!`|-uK<%oL9&As>q!?r+SMCa&C9%5+@N47hC+HW z*tWiRQ(nK;c3~6Orw{B|fc)+1h@e{MkH-^*#ZB1=&wA1t!d(`2*t1EWR)`w`Yq;5E zUL>&=2wAUe7u1iv?9$!y#Ua{3Dy4-6of>4c<7=8si^7RMELogKPj+g&+&J34=b5Ll zn9TWH->VA@%fYEaODV zHe^b@8V8vmZ=(DtYAQSG4%kwsV3bIaN8@m|gGCUaC6Amo|)jPj%3X^or9JsN9;qTA?lD`fAf`V89;101Na^OkF68tquc zp>nGz6teqO9kFhTs;D3h&W%mDXD-(l1B)KmHa@ge+yg+rTzx_b?5+;G+t46#!rI!9 zV61%O{TP>WI4Q zalfonpNrAnCHD}g!|X;eh~TiPt!4}em5)8H=j*xovWfGA`D zo)9KYPOie4QKoA*LDTT&qA5~FF~HntRwz}58pQn-Qmde0R{>a;%i+L`=3 z&owdEPgX&IxZ0=GWF8~)md^(=i=7y|M@x^&uo2~AP6-baQ|(2S78{qdj_waW5yQtYq+00hYyKO&=TrDebhbO0m}f zs6FWjbilcq2xqp{6MVMrmm0LV+L#wJW2y9(Im)m`Z<)tMEv?IXaHoFOsSy&7Yh5=9 znXmF7pU?L|o?vQ~u9~RGgpg(`8N#PEivyXG!?7~ma9ya+%6hV^ioaj;FA z7w}n4@IXdd%WX;=vf}e}pAiv^p>0yPw@e=^{Vp*MN26jO{pIpA3d11tskLrQ8Z?|i zdy`c}LBwJ1mY#^rJ_7BE#mfzSSw3~+J%$d8>|z@TtF$+>=$2?Nhm+GBLxFtjSjVQ| zx-aMRW%*ODtKg*@mcEElS`yg!sJwz*!v~rMDPer#E+jQ}g(Z5*SlBWCyNGt`H3I@)cV2~{xxF$^O?w?9@t z`Kzz~;a~pke ssh-ed25519 vZ8Vgw cJhdOIkMufEIHU+LqOAs4/KTxOiiBL1Knl8ChkApajM -lSJiqVLy8KS0+pD0MSDgtD2IdMD7toVof4u8zFbc534 --> piv-p256 ewCc3w A4oqsiewlX50Psnk7HT3nRGHd3+pdgb21kN8Zk1hPwKa -GAsqMgKtKRCSroHria5qAWSY8XqOUmHDMRCXdLOIJdQ --> piv-p256 6CL/Pw A67ODihOF1IhvLWhUsIWAQVmhO/XTJ6GRznS4GkJwxOO -3zriXUAIS3RLhVDmeCzFka4LerN3/fgIJRyKmJatIFg --> ssh-ed25519 eOAUSg px/iV0OQ9ZtNyNJsPIWdEbmemBKaXqfcD4Ew85HcVAo -7LMHrNse0MjlLNlAwbMexIcSRjK1vDkLSoEfJAtRmLU +-> ssh-ed25519 ZpDcxw Z/XNMwkMr7AXaMpDc/Q7ASWWwv/ID5pRpSUyx66R+2Y +8IWRR7cQVrrBFwAg0zHnbliWZWDKsa2PmdxHZsCFQLM +-> piv-p256 ewCc3w AkDTktwBj1dnAA63otR9BhIAyjcPoSww4G8/cP1zSX+j +i+ftraOs9yZ51gUumFA+zJZzgAPRtVyuPfUsA0hlXnI +-> piv-p256 6CL/Pw AsD6ZBVGl8HU3FQKnuskPk8qFHPgxK+9adJGuuzik88c +11sTCEgccvVjS29h2j3d7PwNZsdPqf4Y/9tagHbE2HM +-> ssh-ed25519 eOAUSg wdE8dGVe35YXCdpXfko+lLsJTgJUmal7OSII3r4Somw +nXbdTQ6KrGbb/FMSiELLuQexNut8qefOIcGyocbYbiA -> ssh-rsa REaZBA -ewC5fAfzqr04uPsw9l3E8PZKoKIDACP1U5lEnL2++04zv7w9GL0eEHa693leB9z0 -OJNhq2FHU+NH2IofoLm/k46ma3XVWaCNExYiSaRQWo7Cm6fON2F+K32tJaxeFc9n -xCE7E5YM75e1U9LOqSPdfoeuWUg5iLDhg4uauMPagCgKA5Dd0ndClZyNWYK2C7NQ -V6MhczLs6cxrFEOQoaudlXnj7TDAiCdVLFmvlas56+QqVA2SXfM+xha3flfkZvR4 -rdcUY/y0ZBoLaZF+K0f2gcqDlL8VgvYFizIPTRAZYEKdMytSJbxv4LFb64E85Lwe -JFWPCp1lBTVhDFeulN7XBYYOCacw1Pyj+n3at2GIPEZwhnl6++NtBuiWj+g3Cnfr -BomwuFlcZmykI3p4qzPlwcHaAeY4UhzciNo+frDMKCUhUmczBoRMK45oa8nv5AC3 -z2h8OZGtKNpg55/FZJ9NONr/GA3xR74iB6chpANx8h6e8QDnUo9FZyl+qZcQ/Ghr +S8twRuHh+Oety2nRkiOyugu5ZgCaaoapLe4UF9CNCy0ldX87t+Y2va/WdAB2q/UU +evMdN8MOplM8UxkQy69I2BlT26M9QktFuo90ktXK35DrFwiaP8a4aKOyiKzXFdcZ +h4F7Af//lMlcwLYFARbAuXTIj5w8/Hd9IvhAbFoqtXo5+a2n6MgHdc5LvlLSjf9v +aP7+01jGcT2lSbmw8YS8+caYE3LVlUh2ThTeFQKYWm+nE78IP19/TOabmNnx8eBX +LtJggwnoNPrwmn53fn1tADI1+6QoIpTIrZOZLhAKnzDc5oasVNyjAY9NGC/U6A8e +9eaPn3KmIg6QBg8hXUCLH4JeGHFU0hIxCeLotoh1F1HEi+CY3tPuylpny08/J3AJ +i3+RvguyuJ3M5zwoMDvmavPw5mcV9tvWYhUSAWd6iZ6DIN1AHFsXQFXQuRn4P+3h +v9faikTZrt/sv4ZQgWKNoTJ6CDxcAnhDkTtlgG4afjreFvRuB7esQz8v+G6BhUcE --> ssh-ed25519 J/iReg IFdFOpdEUjMHdunUN7vfJ09lkDLA/SPOQY4b8UjT0S8 -qQEZnxKcLpbhc3u1zE3QCI8X/XTCRuWOwPsOL2SzdzI --> ssh-ed25519 GNhSGw 6aFCUFK8BqLZrP5m1qGYxdRihpaoJyueHJa/00T/ZHM -Jfa1/KfD1JCYdIsdF6h+FjV3yozWfiscNWv2Krkdjz8 --> ssh-ed25519 eXMAtA sFwZS3oq80e8CfDruygKRW1oP5aGfpHqeOrqEcoRL0o -lYaTFhvGXqVnd7WHFZXe8FZ9pWtk6dL9M6h3DC3Eov4 --> ssh-ed25519 5hXocQ 4eznATzeJliAZkVeR69DD2Gr8YKWcarlSSOEPjk9Cxg -Rd6iAIj7jCs7/sDisd9ErKG63bA6jW07XqY4MdKBGjA --> ssh-ed25519 bRHVVA YSXkgUVhRpXYbbeVpsdLZzncNIysYNrLvc5q3JTaLWI -g9xoh/G4NnKmjXPuLy5flQCjVYGbkAR5RHOqnhi95/U --> ssh-ed25519 HgW9eA 4uEzjQxKg8KIXPGyMRdXdJb4BPC5ZTRok9tAXa6AwwQ -BXnFdGZlCjYakDN81w1fxF2P94b2ZAZAhlBsbM3I3PA ---- ragIvYUEISd+PwMW5KyGj0r7HOZQiHIDbY6vHewuZ1I -KJ׀ % Xplvt!q^aܠt|"&7 \ No newline at end of file +-> ssh-ed25519 J/iReg aY/7+a5eakXhTJcsTR6ES7dTNBbmgh5riO0fhHzN4V8 +g4c9HRqdnw8oW3XZaDzUGl8FiVRe/Zwf/uewt8Henlk +-> ssh-ed25519 GNhSGw pBleL7cbEZQhQRPP6wSLIJ9SmU7xCjdanDzQvzVBuUg +d7QKEOygw4M8ne+iFiU9DxOwBGTC9JNS/2UPx9zjLXs +-> ssh-ed25519 eXMAtA PRMzXld8kf3oLoqORVVeO0THG2g6as9vTwGhznQPZBo +i+Ea7CfolHGjvoDuIeHl4bCF20718pV7WrlES+Sf2bE +-> ssh-ed25519 5hXocQ 0e9eAjPAQee/VZvL2PYm9RgoVBnkOgI/y0pblLvoRW4 +PBfnrfOakv2ACKrDYs/to1zVaYldLiLldaUxn3GY/5k +-> ssh-ed25519 bRHVVA gX62eG+wJesfSBd4lXzTqNMI+FjPl6fnztXygR8HDx0 +mML+owf1iLKGT9ZZkMUu2/l10eSPZEl5TwyAftOOyDA +-> ssh-ed25519 HgW9eA 3Qp1NEa89r/JsT4GKnqDPaTmJaEI1on9D5n5I84DzhE +hH9Kf6l1s1R3zT69iIXmKEB0qPE/i850WMpfb2lk3hE +--- AAABc4YYLcIgv6zjThJ9BhOkR24VZ4wMV+LPR9yTtOM +B.áZ3P5w!)ѝ}[UBrNI^ \ No newline at end of file diff --git a/secrets/mediakiwi/mediawiki-db-pass.age b/secrets/mediakiwi/mediawiki-db-pass.age index 048e90f..7b86501 100644 --- a/secrets/mediakiwi/mediawiki-db-pass.age +++ b/secrets/mediakiwi/mediawiki-db-pass.age @@ -1,33 +1,33 @@ age-encryption.org/v1 --> ssh-ed25519 vZ8Vgw 8qDmlfKzDjDJr49/xY0KW9qOsjw7tZnWr+i0uBARhjI -DQw661vgTzjEBN5jLE56hWu2mjK67AK5MlfE695dPcE --> piv-p256 ewCc3w AvqksWKVJbd7CnRIjs/yKfjhWVBbPO82XJjYVmh/kasN -bh6ofS1lUU++xKVdzQOe3LAs/Rwa66TlitogPHONgAk --> piv-p256 6CL/Pw A/4k87YHJQdTnlMAeQiCDNYawpLXHKc2dHwnVle2dYFR -PVCCARxrMvHqI0GyjS2k78gwigYkj8SttDCCjc3IJDc --> ssh-ed25519 eOAUSg Y7bTQMzWA3tR7k44rsJBZBiks5Okx5JuZMrkyWVpRGg -qVnVRkFYgozvVt0D9rwXi58ZOPuYF8Zfby0q6AHG/MQ +-> ssh-ed25519 ZpDcxw XrXXRdePOdCNAkEtYg5mBAxKKu1EkJOrrD0cUT3JAUc +s7VMCZGoJS5BD8v8/JPQxrLw1GYs01uj9LdR6oL9zsM +-> piv-p256 ewCc3w A5Qzmn7RYXKT13rdPG6MRWX5nvjAgnbLv7oNqbei2kQJ +lHc9/3C/aD5wzm3AQlYqIddEEBtke478hPSem2pZdvA +-> piv-p256 6CL/Pw A+ZT4TG2h/1B1sxTGsz0dk0JI8Aa03rN7JZa7wcLuY2q +WBVW6R65Qm54777ENzWCmIiBKelfF9PjWZEU9QsAPMk +-> ssh-ed25519 eOAUSg u95RB+i9FQ+k6V4IUXgVGBcDNIfExZxpnNQ13iQtM0s +rznvSYERswBrYsoBGOzo67vs7wd6FHSZG8YJlAiNYNk -> ssh-rsa REaZBA -NyxP0jfq3Kmg2JWZDCSfcdOhWlJkJe46lTIfSpSaeMTmSdiBLav8M/R9Kjsp5FtH -vG/880koXjPKO5FyfQyvkq/WEJBY9babY6p8fPmRz2lSKyrYDlDVdE/30KoU/0GE -t/BkPeuCkpE9zKSJWzNKfVRbwMJNRyW7jMHBAVsnSoJfHa0WXrsPD+osN5o3zrwI -37EVokVs8cyDRoEhE7qox6iMiB0IcjDK0OZ2gNbJYVFE2VttnjT91zsNKKlZk/H9 -uPs1BGL2ynsi+CnL9IQX1WlyZUt6r2XLJg54LQhSSNt7sGvjMBXMLdF3I2kNwC4/ -uFmlFNCr/CeCaDpLRIERfm9Y05lsDfB/5lmREgX2G9LMPbCBHDeFDi077cO074oF -6c9NEgC3b8RaW3kZ02fPG84+fTv6UStjb4WDu15AmqxoVYoXiO2dCCvXHU26VSlE -3Cp52woqxt0b6wZanHfPu5+pT0cmBn3KD5TIZwEjB6QKS1aCBHZpfhABDuFu+EPO +jX3byskGzK07aZRbZCUnv8ma8Wwnnav1LszzuKjN5ULBem6tVGk33ak2oRVnNfsc +Vj8jHVySN9b+px1SQk/a/7o/717Xejvoa71xeIMacNuvGNr1VgaISF9niKI5J0Gd +O8KSRcRdkxqBdQ+WIVj1KKjabV9kBxxwf6r1CjH4U1sGqpxCvHT98v20DPYrbzpm +1rR8R+z9v/d75bCmQ/RCzkAfxNhuQkQ0h1xZYuCnlolnRK2TvFbUAO9CcfKCwgia +dwj+R8tt+Pcu5LcSatt7tqqm8X3CKbyb4SJxCHZRq8uz7kDJ5pKKqRIpFL1Gv5jc +2jZEK5tZoKEtnymE7T145Dww3hUdK7lx8Upnm825Unr62DLRNY3JzWnsMpVw0Wkn +tzbul4wLwIkja5388cmpQgHywrTiqtm7uLCs2AeKY6oq/7afVPf2bkgwW+J7bWfH +bDEBavJu/PJa3dYhLR4gdiH3i+rSivbfMbQgfdRCx94liniv2adi6SmpW6dystoL --> ssh-ed25519 J/iReg YE2Ur1V8M0+dSB++jEv5UzOLwLrhb+NYdedUKNrfe2M -w9vGtwGUoYZPuU8cr1EglfRwexdx+aLA5++isPq478Y --> ssh-ed25519 GNhSGw OVh/yHW2aIAKB3xPBlNW7xjKKSO0LjtjAzDCoNxZcyc -0PBtbwz9uTMJF+cDWkRF2m+2Sapnwk3LfDn5izM0MWI --> ssh-ed25519 eXMAtA wku3bHXp5BEozX9pOCh0sLuH+nM/dUKU/Seel733RSk -O8eWnryyVi5lzCg819NYHUcu4rqF12TmE8vh/PnBw4M --> ssh-ed25519 5hXocQ ansyFMSEbq4MWEmCc14EWUdpzjrfreRFdapAKIBUTn0 -41y5NpflbdXvsMPDSR+zNR7+Q6PzOWzI1An6VbN/1yI --> ssh-ed25519 bRHVVA cb/IWLLb6r/6GdE2BcCrfeOYOeHZiU3lig9qaSxNvjE -Wz/Qzrqs+ZcnjI9uPK8AR8pjqEWcALn4MWH+ZS1kF2I --> ssh-ed25519 HgW9eA ynUoOk8XEQIQxW40iZqn6Brzuw1+T7p1zW8yBiDy9Ak -6Udtd/dEPZ43B79K+oLJj3Ek/n3lMniz5XPB04Q5aus ---- Dz0Xb2xUxZcKYSVmILBu1xlgKzi7dvMnIZ6ANpUxqzY -vפ2>"l= "_U|a=|0 ֑HТ!F޵Y9ǟ& \ No newline at end of file +-> ssh-ed25519 J/iReg zCuMlBFN4eVMnh5vHKqvlJlLg6ZcwAy7wHlvVnQABjw +elWuCugOpDB80l/uEOJnNKEem5xoePkyCD3pmELJ8cs +-> ssh-ed25519 GNhSGw Wv9wMTi6QUexDlbucYb9SGS4jNFNFs+hoWa5u5HGmzY +lrswEeBFRfSkHf0hQ4YcEFmV6ytusrk0zdIsMD+QbQ8 +-> ssh-ed25519 eXMAtA 2BfG6GmISd1+jHa9OruAWnNjbOWJ1kSEAABMAWRA/kI +BB3JmIEP6ALQuB41BKQK2Wp8ZZw4XEnP2Boh7icE+Hw +-> ssh-ed25519 5hXocQ hZbub90RRPk0Vas8CCLwdTFxw9DNLbw3o2YRNrKsxAU +VVFz46gOtFCQIQKDeKiuxedT+dt+P9fhY8sTix5uLxY +-> ssh-ed25519 bRHVVA pdEbXrfSgJ2rBKLuHXH0LLnKQy8BbTw0siNm5iLglgY +yRe+LVzvN0l13j4u4K3lGrzn+HokcL2kRw91n1/JIEY +-> ssh-ed25519 HgW9eA tPNgD0elE7Bqh87hYKRlrU0HPgTHlNyqMEMqQfkhlgQ +AtAAtoU/DYVBaQJKPwAcAKrr2KXyJ/iyLUV8LFSmCF0 +--- nTU2XNZl52MtMZI+39D+eVeoX55RSWRERVgJNKti3Qk +J` 긌20RO7v^9tUuj<&bh8iXdWPK*c!IBRxe2(==_`v`w3)Cj&#$BW>EIY1*WRfXLzQ z3T1lHa6d~a9(g3tSXzrQcV#HwRl zL@4uxWR|PR3XDQh$*ODR`Fx@}7FBDtDx*BinW)*^&Xo1{g^NKg2H^907}RNSnh-f(f?Zin_>)yd|pPb2XiGfq9|($UYnICl%Z6xWTAA*h&7P|^%l&D zB=o#i3s@{ZS;|hraNOrNNEXotz+spIFMo1B{g>^SobevsQdgwb^T+1Cm2D>n+NZs^Bg1Dkv*z$z-|g$Vxh| zU?H+%+$A$fkytqCG-)z=P>cd8RzXr}RFg{SvILj6`m`C9(GhcCu)iRti&552xDN zK*eO7TExc!G$VuYT$u?$R6YnOIgFOl(U`>GIjbgb58((L&s1p21sRQumm?!tDC`PT z0dG(NE1gj@;d6_!jNcH+`w>7F3?u=OBqA{|L{*}U8V?A$tmMKnk|iRoZkq8`u4PwgV*28omW&5ob3p$|!Brjux*We-$!j&$SLk1gF zROsl0N6Y4NHqL7dGh`l9OQPCYamoM!ERY5c)r1v%Fv>-vN{k~EV9W_yl-8K4h|oBf z^oo^K`FE4nCW6_BN6x4G2~Sv}l|H+hwz?p-9Yd0!)34733#^`{V*#JqtRPKs4w2zZ zDhH<(5hUw$W;J9~8#I9lFU1>RzO1x@88Uz>0Et7B$nS2W3Dob0L|072 z3TOawAQ&7nmds8Q7p1MLqSvT{gCVoRqK-Q&!!-}wh5mT6$CqsfpEP%R@Tl6x0Da?5n|R@?H< zy7N~L6IT{595nH#{DECJv^$#@$)TC!?v3_(@|8`)R{U|w7qeII|FmvK?ORvDTbJfX zDBX`$KW%Jnzkq6_6P`62_NzNSnzcY`dFR28zG=HYt9ZR>O2E0V>kl_%SIBVh(2kRb z8bK49mR#AC zyH+=-eeCq_SKnIj!V^EL^hfTjp53rTcy#`qrB9z(mhXFheet>_@KWy%;e6YY*?U$s zTJzs-`UHXI+NNDI43S@a?$ur8bMyMl(p^0_xcgpwz(nu#Yn_`@ZBRoarG0nw7<=`} z@bz%Z;Aa~ARitj`lBz!Xud{RqQP<W4o)*#U>paZhvWa zU|asanw!Gt`}TNyH%AVAI&V_dw#3a(#QBqjqVeqblXDzL>bD-);uTB}eE6MaXym&| zBR0(KzZ(B~++(lreAP2{`J#`O318MK+MAcQc}A@Z!jGP7+A*`|H_6tYmfh*Ovg`cK zr6s-3u6=e^X4tBI-F5#E&70yQ<~%(1+Y`$MfN#~l)|Pwc%DPkd!r|jCB}U)B>f!AN z*=ggaPaN21wPAm1{r;~0{L^hYb^8EIa_@nyTYbX&!=szepC0pZ{iqgV;|B}sJ0~B8 z)cf+m$jtuCtfgM8_OAKknpblR)T1U&-E^p9&3mWzxZW73*~SevO4`rAs9W*FD&>=| zW0!W_b~h`VXh16$%8t zxqBqDeEPZzTXrlSb`aUyGUHlH@9j-T?_WP?_zG%wN9(e`80{Bo-apZo@2GPe9;DI? zd2wQo=qp{phL>$4Dwa21u9=<0SGB!XQ)QWO>DlH#y)b0_yB#h2U}5*5hJ*inLqFri z_m6M5_hPxnjiz1`KIno5?kw)+yCNKaZ>e4;-_9<7_n!LvLp>~KhW6^#bLz2^*(K!Z zx}Gf^8~4eM_Zl6USy?=={>+G~9m`&9YTXvNvTEpF&tThE&BJa7^P}pUT9wurD_(DS zbAHWu4b?c({;#7d<0o&8=uXV}cyHT{V~L-A&`3p}G(PRr3p;tZ3t4V_d)1w1z|TIs zyMNDXdmr4e=+eU0cREiNKDcNYTGu$?$+K(UF8S76c0c5Kz zE?(_-S#$DofBE{(Z-?CKT(IlNSaj(TuE#v5X5~Qg>g=hF#XonZhBzL0{G|5sv616O qKGiv^9>;l$%no(A_AT;2j&hDrXWBGv)2>`;liumww5eQankH?MUTM-!*;!9u z9AvpfZqPxILzhcX&J_?4fk99Nbdgi=i3p4ihX}LmzUO

|gMCzwht+{g4b@OJ}G; zeXf|wX6mv9MlA!1+R;i;WNYb!R<8$4N;zi4nUXS`Q3;$otHu6pz9+jWJJ`b0)7@{2!LV;#T_}vU1jXgQj55 zYG|Fc1WFpOPKSg7wu}WqCBkXPFsGm9fQq*qGb=!XlAKW)lLC1MD}dgd5zMM0syGJX zl;AJnE;VBE8r+yjIv!1t_QK&Hqoh)_H|JqkLQkMFL|LF<-e0Nsy}FQ&WKupRL)cI! zEV2+vC_-Gys1e9K=jCJZNQ~CvDU-*7ddXx3!018>LE|`U1Vo>T`;Ar*No4>fV`f+p z7Lc%DG^u!-1BmGY!6240vN2Bp<6MDKEaySh87mF@cswiufG(NL#Tb5|zAB2Bct!#1rmBLh=A9v?RdBbRcd|TM#Ui@%Vx{GOQ_;OB9ToX~7-hVitcE zE(CZ5XtS6z8Tn!U80NL8K{DX60btY!YC*G;H-xAP7?TZdD-#X53j`Y$Vri8FI6`#Ziv~5>k}sQ1$Z;!@%u@zc zDHDyeK#I30#CX0E2qY=i!PrP`B_GFgqT7nfET(2bP(rjoUdj^nP=+$YB|ak}Du4-O zteoDO(Lfj+R7hG0N;&l~YSNPR5uOs#8d~t@jAA+$Gl-I;qR=2qiV=~5B^h^;ZXkn2 z3o(<<c5jm{p86cTQ@^&*&Dv?%iIF<0F{V7Pa@P54-f^tT?!xD(&u5 zda?ouF(Y?j79NVHGZ43Q=JOXj8Y^ud3O+{mnKYocqDNwcAs2l^<@=3#@j zl2XkPfKOk9D}X9gio-dd$s#cT4;ZvkM0DHAD3(sxh%69gbqb#Ks@)~hX#%*27s2CM zgEbyWK*5BFhXX|`WEQwe1hncPgn5)kM=`hvD;ZmT4)N)V(YPJZu4x|OvP_oyX5G*Q4I$u&W(73@bT5NnI_n_cXwDE$=WS&;BtfLFp!1Lr64%!2U9l3xa*CK)Ph*HVjLN8A zi<%L)CQgVR!QspXZF)Ip3|8vdhh5aLCgRcC;sHC3!^!}Ut3iJ*LF;(b>F^2WqQz7Y zV-)U50jSI4aAJ0=(~uI4c8!AcF^VXk1$dmA#aKJjVhY~^dl@;DY-5FB$yYtR5~PzW}Q@VDBw72 zNH}AHkd<7SQj){MD%cqbL`p6a>ZUahdaQnV`><|})6a{0=wY_SFRk11hpNWl)em32``+sK znq0S?#qOltyi~h+=wS0-s?AB=yaOBu=Rsxp~v#$$0~-xRL$QW zE7)(p;%^jhNpmLJJNC~KYt9Vn-O?0aQx|m!7w%8(5o>9DY~XQ!4fI2wx0|`dAiQ;3 z>*3?j+`4Vj;z7?I9FbjlviI&C9j(Qs`?2<@VtM-~1!~NU&u6STJH74lW@qiVJ@Wg< z+9IlNHus)EfARLjKD)jXJ{@)xTVWYI>Gt^f=<}Cxb#>MEhZe)ow92aCbKmNRHIHq% z>D#wUTkAMJzoF){&u+Du_VK$;9;yCm{i3G6yY^0*y8Zq7&+%XO4!uhj)f-0c*BCq8 z5Z`(J+v3$0D%3b~+i2#6olj~nt~7KRr(Sh!=d8V}hpYQm?_YXo&ezR1c3?fZz8$Zu z=L&MUxhRjhG_d=D2V>6fp1u0ZOXI5A8#c|l zzW>sw7pnj0{d)_($3KmKRsEc8$eP{v%H40&omsD>&$GDtgPr3BEI%D`RxFp_eQx(a zMSIQub$=y~URwFZg7m?nuW97gQ>(&H6yNLp`X;OD95Vy!IIi%~$R*j;liu`@{~Yqh zV%Lx5)N~D1Jzjn9?EAy(CY1;M?&hU$uJ64tcJbq(*$orN^!bygy}`GkZO)Np5AN;t zIkR2$A743qGCXw0+Hr1l>D+v$txr8uTCoj&aIe?S3C=zIfTo|TF5H>${VC7OTAFJ) zbaBhn8#@**>firb?;R&$|A3wwYvA=ncCF^kJC#hYW%j{OgxBr+7<#fLuVNSk#Tu|K|Bp$kc|My2PHcUI*A#rx`(}ray4hVnHTov5Su>KHXO3=r{hA$>;j~xy{K|Z`kw3m0d&nQ!Bm% z(xWGBoiuUl39tBh=lHRzQ?Fee)zEmKjE|j%wvBc@uJt{!Y;>=q^G}aB7onb7nEznV lb<51L?L)4$&FS|`jqeoq?1EV)drRkqCx*`MvL$iW^Dib?PH_ML diff --git a/secrets/mediakiwi/mediawiki-oauth.age b/secrets/mediakiwi/mediawiki-oauth.age index e442571d799062c48266bba318cd37f6914617ec..cbb010a92f791adeb405bd8f87f1a60f7dd73fa5 100644 GIT binary patch literal 2000 zcmZA1{p%zJ9S88F%WO;P)RHoE74&`>zRvT`EQ&Qdv-7m`JiD{IQ&h9Fv-7aK^RV+g z{!$6&^?;Q^R#ayx!J#SrAl7C1L-teBU4n(dDHRB34= z@?!VUrbU>T*4&?FOK~|H&ZeA=( z!>eSH9!-EIUY0~osGh*=1V+yqwGmsS7HiwqEuav{ zgIP`W7^l6JKtVAMb}B>Kc>}Eoe8730A#}c|`Vrc5kPVHa0GQEx79ti!$Ys=QgLDwa zw+)i{vy~GMOa;$k42DSi&We?NQ8Rks=rNXuxf>@iyc2im2crMsL ztso7%S|>W_i4!A5_%KZTZVOTl$_k^XW9^3PRn49VY`N19IZO;NiOgzhvV;Y>qJfw~ ztBkkuH1A}nZXzTHvnZ;zI-7Q7w^1-Z^?K<5Sw)+zy{cHjLuQ~|0cz7(hKG=-gy^KI zAFvskdX2<6T_RU#9-)|fID~ZB4Ft1Aof>;8U;p==|F=9i zB`~?V&w9YVcJU2sKx7N0uIs32C3hs=*jq^;0%I+e5hMh|x!zNfkt(ZGslsiC4JPT7 zH6}?zWaxIOZ&-6NLJ!_0bQ*Gyd9FsI2%?DX|A5&6+t8^mkYaf7&w!o$)wjs~zPnskM@N07nWT9j#3Q#eGkR7G+^(c^eobIWXk^W0&7A9tyu-R#@7I^k zdy{XU=>6k?N6)Iq?l}itWlmk@_2bu_xsW;!(v9>R`JF@My21W?PdyBS?^FNzc>DBA z7YXmdKWblo3A+EG=TE-*RQk6+eE!Me9mErJ;>6L>Lw|qd+H3EX9xKnRuXk>F`ljUByZTGt`s(TX&J^#4E?oP}$8LS}#yeiQ@1x5LKmFSM7k9|b&xT@8O2vc#e5Q>t6LGqij+3{nn@AVn~|XyFtOiHBM{iFbt9JK%OTT zU@g0x5GPIsaTdY0gb&w!Y3?a4P>58Ls&Qmd8ss>WE(9)gsYuMC?SmA%H*_V+hEn6j zqy15e-kj7Fze72`(b5~ULD-lKX9c~?hK=H(4Jl`V7%SAcxz6 z_zrWjZr=uTp)wQ3NCyin@OVh&mhl9s)@URt3dQMeLAxQa!{}wr*J3sc5VcU-vC zr6Th^*=?-UhPxY~X!R$uwoFAnrFC7HR$Qf&@fwwME*oDou+C&a6nOsscwP z=)&sNsb=qOA~gd?!gonV>nm8SR2i7&DaA$J}I) zgQaQ(f+nRS#>5k`Z8^^Ll@X(}Rzqbt!A&I^l7Vcmx@BGyEY8dkT$8qtUrnZUtQtK? zFKpgai8Wg7xOA>*wb_hkZj?s2I~~nHrdU^9K*M{wY5AT>+a;j|V3^nvx`Et?PU~o~ zt}_IwbHok#LFuj5EV)InJ~1DymR-SVLxcxFa=IfYBC837F7MWcja5yQYc$5s`f(X( zOkl<&%vh_ctxFd}^Twz5jM>NWzFe{c5jgsYQz(=@zq5)NzQ*i%%Xb-m@pvS#|5T~IC^j+SOzm!u##>gx5x?`uxGfxAc5 zm|37O`wlEGF})p?lL_C_1lk+Vl@PR}CDE2B-c%?Q3OET4bWHI9XO&0^Q91cM0#h4< z90N*lg{Oo?FC3*5$Bh;o7|o1z_G+R3d#b#JK$c`@fNr?~)ko%Wo#?(2uC{RmtID4B z_+>y>iYF7!e&ek-6BCef#4Fh-n~pTnaU?s^l5)e%N611E2Ky=mJvYpMl1kw#7b-2(+)F3>pbu?M&h@{)-MyR$tB#+&r*(Sa*@W>8_ z2NbU!PUwoQL!R%orMAhthl2_n92|5sQ8SVSxpdIXz^C@oY(TWxWl~PsYfN)$%i>B>fdhEZ z1x}s$MCXsqg`d9nvL&l`UBB+U@u5q_=N>)v*|)#*Df#cRcdMlaFB1Q}6G+e9^{jb? zg#Z2e^~au-;`{!9{Qh3OJU@T%lMmeS81gcF{Mg0&&+e+fep{K`{Jk3S@y}7AG-U( zS1+CU&<)JR;NtH-IN$!N^U@1%J$>y>Kfd~n^3t8ur{a%(=JXTJ1IJ#x|D_90U%Txl z?RfaSc Date: Thu, 13 Aug 2026 22:43:21 +0200 Subject: [PATCH 75/77] Hotfix posgresql (mediawiki pkg et WSOAuth), rollback popups Fix pass tealc --- hosts/vm/mediakiwi/WSOAuth.patch | 11 + hosts/vm/mediakiwi/mediawiki-patch.nix | 852 ++++++++++++++++++++++++ hosts/vm/mediakiwi/mediawiki.nix | 9 +- secrets/mediakiwi/mediawiki-db-pass.age | 60 +- 4 files changed, 900 insertions(+), 32 deletions(-) create mode 100644 hosts/vm/mediakiwi/mediawiki-patch.nix diff --git a/hosts/vm/mediakiwi/WSOAuth.patch b/hosts/vm/mediakiwi/WSOAuth.patch index 5da4498..829e86c 100644 --- a/hosts/vm/mediakiwi/WSOAuth.patch +++ b/hosts/vm/mediakiwi/WSOAuth.patch @@ -1,3 +1,14 @@ +diff --git a/sql/postgres/table_wsoauth_multiauth_mappings.sql b/sql/postgres/table_wsoauth_multiauth_mappings.sql +index d2917db..b903ba7 100644 +--- a/sql/postgres/table_wsoauth_multiauth_mappings.sql ++++ b/sql/postgres/table_wsoauth_multiauth_mappings.sql +@@ -1,5 +1,5 @@ + CREATE TABLE /*_*/wsoauth_multiauth_mappings ( +- wsoauth_user int unsigned NOT NULL, ++ wsoauth_user int NOT NULL, + wsoauth_remote_name varchar(512) NOT NULL, + wsoauth_provider_id varchar(255) NOT NULL, + PRIMARY KEY (wsoauth_remote_name, wsoauth_provider_id) diff --git a/src/WSOAuth.php b/src/WSOAuth.php index 3a94c87..e077b9e 100644 --- a/src/WSOAuth.php diff --git a/hosts/vm/mediakiwi/mediawiki-patch.nix b/hosts/vm/mediakiwi/mediawiki-patch.nix new file mode 100644 index 0000000..9e6228f --- /dev/null +++ b/hosts/vm/mediakiwi/mediawiki-patch.nix @@ -0,0 +1,852 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + + inherit (lib) + mkDefault + mkEnableOption + mkPackageOption + mkForce + mkIf + mkMerge + mkOption + ; + inherit (lib) + concatStringsSep + literalExpression + mapAttrsToList + optional + optionals + optionalString + types + ; + + cfg = config.services.mediawiki; + fpm = config.services.phpfpm.pools.mediawiki; + user = "mediawiki"; + group = + if cfg.webserver == "apache" then + config.services.httpd.group + else if cfg.webserver == "nginx" then + config.services.nginx.group + else + "mediawiki"; + + cacheDir = "/var/cache/mediawiki"; + stateDir = "/var/lib/mediawiki"; + + toolsPath = pkgs.symlinkJoin { + name = "mediawiki-path"; + paths = cfg.path; + }; + + pkg = pkgs.stdenv.mkDerivation rec { + pname = "mediawiki-full"; + inherit (src) version; + src = cfg.package; + + installPhase = '' + mkdir -p $out + cp -r * $out/ + + substituteInPlace $out/share/mediawiki/includes/config-schema.php \ + --replace-fail "/usr/bin/" "${toolsPath}/bin/" \ + --replace-fail "\$path/" "${toolsPath}/bin/" + + # try removing directories before symlinking to allow overwriting any builtin extension or skin + ${concatStringsSep "\n" ( + mapAttrsToList (k: v: '' + rm -rf $out/share/mediawiki/skins/${k} + ln -s ${v} $out/share/mediawiki/skins/${k} + '') cfg.skins + )} + + ${concatStringsSep "\n" ( + mapAttrsToList (k: v: '' + rm -rf $out/share/mediawiki/extensions/${k} + ln -s ${ + if v != null then v else "$src/share/mediawiki/extensions/${k}" + } $out/share/mediawiki/extensions/${k} + '') cfg.extensions + )} + ''; + }; + + mediawikiScripts = + pkgs.runCommand "mediawiki-scripts" + { + nativeBuildInputs = [ pkgs.makeWrapper ]; + preferLocalBuild = true; + } + '' + mkdir -p $out/bin + makeWrapper ${cfg.phpPackage}/bin/php $out/bin/mediawiki-maintenance \ + --set MEDIAWIKI_CONFIG ${mediawikiConfig} \ + --add-flags ${pkg}/share/mediawiki/maintenance/run.php + + for i in changePassword createAndPromote deleteUserEmail renameUser resetUserEmail userOptions edit nukePage update importDump run; do + script="$out/bin/mediawiki-$i" + cat <<'EOF' >"$script" + #!${pkgs.runtimeShell} + become=(exec) + if [[ "$(id -u)" != ${user} ]]; then + become=(exec /run/wrappers/bin/sudo -u ${user} --) + fi + "${"$"}{become[@]}" ${placeholder "out"}/bin/mediawiki-maintenance \ + EOF + if [[ "$i" != "run" ]]; then + echo " ${pkg}/share/mediawiki/maintenance/$i.php \"\$@\"" >>"$script" + else + echo " ${pkg}/share/mediawiki/maintenance/\$1.php \"\''${@:2}\"" >>"$script" + fi + chmod +x "$script" + done + ''; + + dbAddr = + if cfg.database.type == "postgres" then + "${if cfg.database.socket == null then cfg.database.host else cfg.database.socket}" + else if cfg.database.socket == null then + "${cfg.database.host}:${toString cfg.database.port}" + else if cfg.database.type == "mysql" then + "${cfg.database.host}:${cfg.database.socket}" + else + throw "Unsupported database type: ${cfg.database.type} for socket: ${cfg.database.socket}"; + + mediawikiConfig = pkgs.writeTextFile { + name = "LocalSettings.php"; + checkPhase = '' + ${cfg.phpPackage}/bin/php --syntax-check "$target" + ''; + text = + let + dbSettings = + if cfg.database.type == "sqlite" then + '' + $wgSQLiteDataDir = "${cfg.database.path}"; + '' + else + '' + $wgDBserver = "${dbAddr}"; + $wgDBport = "${toString cfg.database.port}"; + $wgDBuser = "${cfg.database.user}"; + ${optionalString ( + cfg.database.passwordFile != null + ) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"} + + ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) '' + # MySQL specific settings + $wgDBprefix = "${cfg.database.tablePrefix}"; + ''} + + ${optionalString (cfg.database.type == "mysql") '' + # MySQL table options to use during installation or update + $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary"; + ''} + ''; + in + '' + . + ''; + }; + + socket = mkOption { + type = types.nullOr types.path; + default = + if (cfg.database.type == "mysql" && cfg.database.createLocally) then + "/run/mysqld/mysqld.sock" + else if (cfg.database.type == "postgres" && cfg.database.createLocally) then + "/run/postgresql" + else + null; + defaultText = literalExpression "/run/mysqld/mysqld.sock"; + description = "Path to the unix socket file to use for authentication. Used only if database type is not SQLite."; + }; + + createLocally = mkOption { + type = types.bool; + default = cfg.database.type == "mysql" || cfg.database.type == "postgres"; + defaultText = literalExpression "true"; + description = '' + Create the database and database user locally. + This currently only applies if database type "mysql" or "postgres" is selected. + ''; + }; + }; + + nginx.hostName = mkOption { + type = types.str; + example = literalExpression "wiki.example.com"; + default = "localhost"; + description = '' + The hostname to use for the nginx virtual host. + This is used to generate the nginx configuration. + ''; + }; + + httpd.virtualHost = mkOption { + type = types.submodule { }; # (import ../web-servers/apache-httpd/vhost-options.nix); + example = literalExpression '' + { + hostName = "mediawiki.example.org"; + adminAddr = "webmaster@example.org"; + forceSSL = true; + enableACME = true; + } + ''; + description = '' + Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`. + See [](#opt-services.httpd.virtualHosts) for further information. + ''; + }; + + poolConfig = mkOption { + type = + with types; + attrsOf (oneOf [ + str + int + bool + ]); + default = { + "pm" = "dynamic"; + "pm.max_children" = 32; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 2; + "pm.max_spare_servers" = 4; + "pm.max_requests" = 500; + }; + description = '' + Options for the MediaWiki PHP pool. See the documentation on `php-fpm.conf` + for details on configuration directives. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + description = '' + Any additional text to be appended to MediaWiki's + LocalSettings.php configuration file. For configuration + settings, see . + ''; + default = ""; + example = '' + $wgEnableEmail = false; + ''; + }; + + }; + }; + + imports = [ + (lib.mkRenamedOptionModule + [ "services" "mediawiki" "virtualHost" ] + [ "services" "mediawiki" "httpd" "virtualHost" ] + ) + ]; + + config = mkIf cfg.enable { + + assertions = [ + { + assertion = + cfg.database.createLocally -> (cfg.database.type == "mysql" || cfg.database.type == "postgres"); + message = "services.mediawiki.createLocally is currently only supported for database type 'mysql' and 'postgres'"; + } + { + assertion = + cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user; + message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true"; + } + { + assertion = cfg.database.createLocally -> cfg.database.socket != null; + message = "services.mediawiki.database.socket must be set if services.mediawiki.database.createLocally is set to true"; + } + { + assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; + message = "a password cannot be specified if services.mediawiki.database.createLocally is set to true"; + } + ]; + + warnings = + lib.optional + ( + cfg.database.type == "sqlite" + && ( + cfg.database.host != null + || cfg.database.port != null + || cfg.database.user != null + || cfg.database.passwordFile != null + || cfg.database.socket != null + ) + ) + '' + The services.mediawiki.database options host, port, user, passwordFile, and socket will be ignored because services.mediawiki.database.type is "sqlite". + '' + ++ lib.optional (cfg.database.type != "sqlite" && cfg.database.path != null) '' + The services.mediawiki.database.path option will be ignored because services.mediawiki.database.type is not "sqlite". + '' + ++ lib.optional (cfg.database.type != "mysql" && cfg.database.tablePrefix != null) '' + The services.mediawiki.database.tablePrefix option has no effect when the services.mediawiki.database.type is not "mysql". + ''; + + services.mediawiki = { + path = with pkgs; [ + diffutils + imagemagick + ]; + skins = { + MonoBook = "${cfg.package}/share/mediawiki/skins/MonoBook"; + Timeless = "${cfg.package}/share/mediawiki/skins/Timeless"; + Vector = "${cfg.package}/share/mediawiki/skins/Vector"; + }; + }; + + services.mysql = mkIf (cfg.database.type == "mysql" && cfg.database.createLocally) { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { + name = cfg.database.user; + ensurePermissions = { + "${cfg.database.name}.*" = "ALL PRIVILEGES"; + }; + } + ]; + }; + + services.postgresql = mkIf (cfg.database.type == "postgres" && cfg.database.createLocally) { + enable = true; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { + name = cfg.database.user; + ensureDBOwnership = true; + } + ]; + }; + + services.phpfpm.pools.mediawiki = { + inherit user group; + phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}"; + phpPackage = cfg.phpPackage; + settings = + ( + if (cfg.webserver == "apache") then + { + "listen.owner" = config.services.httpd.user; + "listen.group" = config.services.httpd.group; + } + else if (cfg.webserver == "nginx") then + { + "listen.owner" = config.services.nginx.user; + "listen.group" = config.services.nginx.group; + } + else + { + "listen.owner" = user; + "listen.group" = group; + } + ) + // cfg.poolConfig; + }; + + services.httpd = lib.mkIf (cfg.webserver == "apache") { + enable = true; + extraModules = [ "proxy_fcgi" ]; + virtualHosts.${cfg.httpd.virtualHost.hostName} = mkMerge [ + cfg.httpd.virtualHost + { + documentRoot = mkForce "${pkg}/share/mediawiki"; + extraConfig = '' + + + + SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" + + + + Require all granted + DirectoryIndex index.php + AllowOverride All + + '' + + optionalString (cfg.uploadsDir != null) '' + Alias "/images" "${cfg.uploadsDir}" + + Require all granted + + ''; + } + ]; + }; + # inspired by https://www.mediawiki.org/wiki/Manual:Short_URL/Nginx + services.nginx = lib.mkIf (cfg.webserver == "nginx") { + enable = true; + virtualHosts.${config.services.mediawiki.nginx.hostName} = { + root = "${pkg}/share/mediawiki"; + locations = { + "~ ^/w/(index|load|api|thumb|opensearch_desc|rest|img_auth)\\.php$".extraConfig = '' + rewrite ^/w/(.*) /$1 break; + include ${config.services.nginx.package}/conf/fastcgi.conf; + fastcgi_index index.php; + fastcgi_pass unix:${config.services.phpfpm.pools.mediawiki.socket}; + ''; + "/w/images/".alias = withTrailingSlash cfg.uploadsDir; + # Deny access to deleted images folder + "/w/images/deleted".extraConfig = '' + deny all; + ''; + # MediaWiki assets (usually images) + "~ ^/w/resources/(assets|lib|src)".extraConfig = '' + rewrite ^/w(/.*) $1 break; + add_header Cache-Control "public"; + expires 7d; + ''; + # Assets, scripts and styles from skins and extensions + "~ ^/w/(skins|extensions)/.+\\.(css|js|gif|jpg|jpeg|png|svg|wasm|ttf|woff|woff2)$".extraConfig = '' + rewrite ^/w(/.*) $1 break; + add_header Cache-Control "public"; + expires 7d; + ''; + + # Handling for Mediawiki REST API, see [[mw:API:REST_API]] + "/w/rest.php/".tryFiles = "$uri $uri/ /w/rest.php?$query_string"; + + # Handling for the article path (pretty URLs) + "/wiki/".extraConfig = '' + rewrite ^/wiki/(?.*)$ /w/index.php; + ''; + + # Explicit access to the root website, redirect to main page (adapt as needed) + "= /".extraConfig = '' + return 301 /wiki/; + ''; + + # Every other entry point will be disallowed. + # Add specific rules for other entry points/images as needed above this + "/".extraConfig = '' + return 404; + ''; + }; + }; + }; + + systemd.tmpfiles.rules = [ + "d '${stateDir}' 0750 ${user} ${group} - -" + "d '${cacheDir}' 0750 ${user} ${group} - -" + ] + ++ optionals (cfg.uploadsDir != null) [ + "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -" + "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -" + ]; + + systemd.services.mediawiki-init = { + wantedBy = [ "multi-user.target" ]; + before = [ "phpfpm-mediawiki.service" ]; + after = + optional (cfg.database.type == "mysql" && cfg.database.createLocally) "mysql.service" + ++ optional (cfg.database.type == "postgres" && cfg.database.createLocally) "postgresql.target"; + script = + let + dbOptions = + if cfg.database.type == "sqlite" then + '' + --dbpath ${lib.escapeShellArg cfg.database.path} \ + --dbname ${lib.escapeShellArg cfg.database.name} \ + '' + else + '' + --dbserver ${lib.escapeShellArg dbAddr} \ + --dbport ${toString cfg.database.port} \ + --dbname ${lib.escapeShellArg cfg.database.name} \ + ${ + optionalString ( + cfg.database.tablePrefix != null + ) "--dbprefix ${lib.escapeShellArg cfg.database.tablePrefix}" + } \ + --dbuser ${lib.escapeShellArg cfg.database.user} \ + ${ + optionalString ( + cfg.database.passwordFile != null + ) "--dbpassfile ${lib.escapeShellArg cfg.database.passwordFile}" + } \ + ''; + in + '' + if ! test -e "${stateDir}/secret.key"; then + tr -dc A-Za-z0-9 /dev/null | head -c 64 > ${stateDir}/secret.key + fi + + ${optionalString (cfg.database.type == "sqlite") "mkdir -p ${cfg.database.path}"} + + echo "exit( \$this->getPrimaryDB()->tableExists( 'user' ) ? 1 : 0 );" | \ + ${cfg.phpPackage}/bin/php ${pkg}/share/mediawiki/maintenance/run.php eval --conf ${mediawikiConfig} && \ + ${cfg.phpPackage}/bin/php ${pkg}/share/mediawiki/maintenance/run.php ${pkg}/share/mediawiki/maintenance/install.php \ + --confpath /tmp \ + --scriptpath / \ + ${dbOptions} \ + --dbtype ${cfg.database.type} \ + --passfile ${lib.escapeShellArg cfg.passwordFile} \ + ${lib.escapeShellArg cfg.name} \ + admin + + ${cfg.phpPackage}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick --skip-external-dependencies + ''; + + serviceConfig = { + Type = "oneshot"; + User = user; + Group = group; + PrivateTmp = true; + }; + }; + + systemd.services.httpd.after = + optional ( + cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "mysql" + ) "mysql.service" + ++ optional ( + cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "postgres" + ) "postgresql.target"; + + users.users.${user} = { + inherit group; + isSystemUser = true; + }; + users.groups.${group} = { }; + + environment.systemPackages = [ mediawikiScripts ]; + }; +} diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index a578d46..17faefe 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -8,6 +8,10 @@ let phpExtensions = config.services.mediawiki.phpPackage.extensions; in { + # Mauvaise config posgresql, on fixe à la mano temporairement + disabledModules = [ "services/web-apps/mediawiki.nix" ]; + imports = [ ./mediawiki-patch.nix ]; + age.secrets.mediawiki-admin-passwd = { file = ../../../secrets/mediakiwi/mediawiki-admin-passwd.age; owner = "mediawiki"; @@ -23,6 +27,7 @@ in owner = "mediawiki"; }; + # Attention : il ne faut pas mettre de retour à la ligne à la fin du mot de passe ! age.secrets.mediawiki-db = { file = ../../../secrets/mediakiwi/mediawiki-db-pass.age; owner = "mediawiki"; @@ -302,10 +307,10 @@ in name = "Popups"; owner = "wikimedia"; repo = "mediawiki-extensions-Popups"; - rev = "cfe78f2c07fa5cf6edcb109eadd8b4b01df58d92"; + rev = "82742d6e42750bf5b8000c90fc894b304aa95235"; # Le SHA doit être changé à chaque nouveau commit de traduction. # Pas de meilleure solution à ma connaissance pour suivre les releases. - sha256 = "sha256-lE64Tl0KmUoNHnN2bkNYvhexz5s1wMQ1bDvWSoWTdZU="; + sha256 = "sha256-mqiDU7w3B3imMLqoTvwgnyk/vCDWeAiTEX1y4dWmNDk="; }; # Auth diff --git a/secrets/mediakiwi/mediawiki-db-pass.age b/secrets/mediakiwi/mediawiki-db-pass.age index 7b86501..9c1c9e7 100644 --- a/secrets/mediakiwi/mediawiki-db-pass.age +++ b/secrets/mediakiwi/mediawiki-db-pass.age @@ -1,33 +1,33 @@ age-encryption.org/v1 --> ssh-ed25519 ZpDcxw XrXXRdePOdCNAkEtYg5mBAxKKu1EkJOrrD0cUT3JAUc -s7VMCZGoJS5BD8v8/JPQxrLw1GYs01uj9LdR6oL9zsM --> piv-p256 ewCc3w A5Qzmn7RYXKT13rdPG6MRWX5nvjAgnbLv7oNqbei2kQJ -lHc9/3C/aD5wzm3AQlYqIddEEBtke478hPSem2pZdvA --> piv-p256 6CL/Pw A+ZT4TG2h/1B1sxTGsz0dk0JI8Aa03rN7JZa7wcLuY2q -WBVW6R65Qm54777ENzWCmIiBKelfF9PjWZEU9QsAPMk --> ssh-ed25519 eOAUSg u95RB+i9FQ+k6V4IUXgVGBcDNIfExZxpnNQ13iQtM0s -rznvSYERswBrYsoBGOzo67vs7wd6FHSZG8YJlAiNYNk +-> ssh-ed25519 ZpDcxw EYeeHlk7G0Ce8Zjz7DOA6umVE58ZNeRb1ao86/romSA +iCLH6+fsNCeM5heK2QFh9kEY44BiDOQ8um4MCNUDeuI +-> piv-p256 ewCc3w A+QtLSWeA4j0756rlETtyvJRiZR3y70+UMs/X1/u0fqq +FslsT1yQ+NNHK8m/eBLWeNcsPu2BnubNO1h/AO95UxY +-> piv-p256 6CL/Pw AxHghSnLJmHJz9rXlP1IQDoigFTkFUQiE7h3c0EMeLsc +bmI1TbflKxJ6Axg/TgDEtCV0pw7tY6V/pMl4CJGjvL8 +-> ssh-ed25519 eOAUSg TPhT5qmh2Y9b0eBa41uTdIDdZEcDZtR6tEV8Jo1MuCk +gBD5CQlnxMYohS13RvzoDDQeGDhYOEcS18Xv1S/5f+M -> ssh-rsa REaZBA -jX3byskGzK07aZRbZCUnv8ma8Wwnnav1LszzuKjN5ULBem6tVGk33ak2oRVnNfsc -Vj8jHVySN9b+px1SQk/a/7o/717Xejvoa71xeIMacNuvGNr1VgaISF9niKI5J0Gd -O8KSRcRdkxqBdQ+WIVj1KKjabV9kBxxwf6r1CjH4U1sGqpxCvHT98v20DPYrbzpm -1rR8R+z9v/d75bCmQ/RCzkAfxNhuQkQ0h1xZYuCnlolnRK2TvFbUAO9CcfKCwgia -dwj+R8tt+Pcu5LcSatt7tqqm8X3CKbyb4SJxCHZRq8uz7kDJ5pKKqRIpFL1Gv5jc -2jZEK5tZoKEtnymE7T145Dww3hUdK7lx8Upnm825Unr62DLRNY3JzWnsMpVw0Wkn -tzbul4wLwIkja5388cmpQgHywrTiqtm7uLCs2AeKY6oq/7afVPf2bkgwW+J7bWfH -bDEBavJu/PJa3dYhLR4gdiH3i+rSivbfMbQgfdRCx94liniv2adi6SmpW6dystoL +NtWXPRN/qPJ5XUZNXt64Rt3brMfbXxP2ARvXo3wpnB0ed0QPnX29OTyVm/O2VNrY +5jPouFaxWwDBuwyJ/HyJJB5SdrE188u7du/M0Z8l1/H8i2lpkp98XS64AEXkvrHo +oWFsRloCWyeIU3m1MtUdqu/c3pnqyiE1SlCnajb6MUUrhDe2CdEEgJxFsIyMwXpW +mkwtHxe9ngVebRLY3vw9OVhtqrKeKrNvSGGCE+IFn6kWuNoGD/rDI52kx76sNa0D +531FFAdsiPt1VKZi0YBaUfRP407sc6rw8R258jFCeYpa58Zud62BWsT0d464gPkD +tJQUZecCPRW0jflpVWzqZHIVurtp3Dqjp/oovmMMJ303/ywL0VdZR6PeMndGOkf+ +ame4zqTwOyQoxSkU+62D/KeSzM0QlVqAZhL+lMfYkFCYizZYQZSlLAHi7nF3704W +S+nrv+FNNxLYT6zlq0hHokP9fIfUct/eFfMmV2ff9E77Tow/79TpgQnfxxdtJWJG --> ssh-ed25519 J/iReg zCuMlBFN4eVMnh5vHKqvlJlLg6ZcwAy7wHlvVnQABjw -elWuCugOpDB80l/uEOJnNKEem5xoePkyCD3pmELJ8cs --> ssh-ed25519 GNhSGw Wv9wMTi6QUexDlbucYb9SGS4jNFNFs+hoWa5u5HGmzY -lrswEeBFRfSkHf0hQ4YcEFmV6ytusrk0zdIsMD+QbQ8 --> ssh-ed25519 eXMAtA 2BfG6GmISd1+jHa9OruAWnNjbOWJ1kSEAABMAWRA/kI -BB3JmIEP6ALQuB41BKQK2Wp8ZZw4XEnP2Boh7icE+Hw --> ssh-ed25519 5hXocQ hZbub90RRPk0Vas8CCLwdTFxw9DNLbw3o2YRNrKsxAU -VVFz46gOtFCQIQKDeKiuxedT+dt+P9fhY8sTix5uLxY --> ssh-ed25519 bRHVVA pdEbXrfSgJ2rBKLuHXH0LLnKQy8BbTw0siNm5iLglgY -yRe+LVzvN0l13j4u4K3lGrzn+HokcL2kRw91n1/JIEY --> ssh-ed25519 HgW9eA tPNgD0elE7Bqh87hYKRlrU0HPgTHlNyqMEMqQfkhlgQ -AtAAtoU/DYVBaQJKPwAcAKrr2KXyJ/iyLUV8LFSmCF0 ---- nTU2XNZl52MtMZI+39D+eVeoX55RSWRERVgJNKti3Qk -J` 긌20RO7 ssh-ed25519 J/iReg ZAxHEQjYmqQkMFth5Lp344f0JqwfGh4CO6DEG+uMmys +qlLlf9eLNNw3/o++6LVpy2Anj3TWkNbcJoJ/rP4JKsI +-> ssh-ed25519 GNhSGw 44kEvp1o41BaNRwFnAjKB4K3otIYhN7QN9FgCd+eSQ4 +fqZM/JCIderSDDrxckv4VLZu64Dfx275UVmfKsozpKU +-> ssh-ed25519 eXMAtA mPn1SPpt7RkhMBLqUZ4mWGvqNeGtUN+pcA9jjKKGPGU +bGyck1ZRHRlzk+HVmL3e99MiVFxjTuC7v/6WHjRjH8A +-> ssh-ed25519 5hXocQ bT0MsqVcYEcB/ckkuAC0zbH5+pfs4iPso9MHhVUyx2U +jZQOFmGWnZMtdTS5ePKIvnQFiOVsZTC/A09ZmXzveho +-> ssh-ed25519 bRHVVA NSnH4xuAWnDgUIpi+csZOvq0K6u99Dt1IUGDrzZGcgQ +L9WOetohzpDPAcF/1jGPmWnBhazSDyIU/0or+il2/ug +-> ssh-ed25519 HgW9eA LdB9v8WGmExJffyQa8KuudNr97nyxshWj9j0YMDJKlI +0BCRYHWmulVjwtlYUR2Xi4rdnLSRQTLL2jn8N5rubSo +--- y4i6KM8sEq07Sx594lC7WX9hEtq9ce4Vm9tTU8loJtc +2Ջr};,"ív&d}8U_VYnhZħg`Nf` \ No newline at end of file From 023bb05b08522e006d183dc405abd90bf905daaa Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sat, 15 Aug 2026 15:29:35 +0200 Subject: [PATCH 76/77] User email et local login --- hosts/vm/mediakiwi/mediawiki.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 17faefe..5883372 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -150,7 +150,7 @@ in $wgPasswordSender = 'wiki@crans.org'; # C’est un alias de root (pas de réponse) $wgEmergencyContact = 'contact@crans.org'; $wgNoReplyAddress = 'contact@crans.org'; # On reçoit les réponses aux mails - $wgEnableUserEmail = false; + $wgEnableUserEmail = true; # On active les emails aux utilisateurices mais restreint via les ACLs $wgAllowHTMLEmail = true; $wgEnotifUseRealName = true; # On active les noms réels pour plus de lisibilité avec les comptes note @@ -163,7 +163,7 @@ in $wgUsersNotifiedOnAllChanges = [ 'Ninja_wiki_notifs' ]; # Auth - $wgPluggableAuth_EnableLocalLogin = true; + $wgPluggableAuth_EnableLocalLogin = false; $LDAPAuthentication2AllowLocalLogin = true; $LDAPProviderDomainConfigs = "${config.age.secrets.mediawiki-ldap.path}"; $wgOAuthCustomAuthProviders = [ @@ -197,6 +197,8 @@ in $wgGroupPermissions['*']['createaccount'] = false; # Restrict the creation of account to sysop only $wgGroupPermissions['*']['autocreateaccount'] = true; # Création de comptes depuis LDAP et Note mais pas local $wgGroupPermissions['*']['delete'] = true; # Création de comptes depuis LDAP et Note mais pas local + $wgGroupPermissions['user']['sendemail'] = false; + $wgGroupPermissions['sysop']['sendemail'] = true; # On réserve l’envoie de mail aux admins $wgCategoryLockdownWhitelist = [ "Spécial:Connexion", "Spécial:Déconnexion", From f4ba82ca6b6c8bf758df345d50c522321a079d6d Mon Sep 17 00:00:00 2001 From: Pyjacpp Date: Sat, 15 Aug 2026 17:11:49 +0200 Subject: [PATCH 77/77] Augmentation max category tree --- hosts/vm/mediakiwi/mediawiki.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hosts/vm/mediakiwi/mediawiki.nix b/hosts/vm/mediakiwi/mediawiki.nix index 5883372..fcdc3c4 100644 --- a/hosts/vm/mediakiwi/mediawiki.nix +++ b/hosts/vm/mediakiwi/mediawiki.nix @@ -219,7 +219,8 @@ in $wgPdfInfo = '${pkgs.poppler-utils}/bin/pdfinfo'; $wgPdftoText = '${pkgs.poppler-utils}/bin/pdftotext'; $wgScribuntoDefaultEngine = 'luasandbox'; - + # 4 pour les catégories, 3 pour le reste https://www.mediawiki.org/wiki/Extension:CategoryTree + $wgCategoryTreeMaxDepth = [10 => 3, 20 => 3, 0 => 4, 100 => 3]; # SyntaxHighlight extension $wgPygmentizePath = '${pkgs.python3Packages.pygments}/bin/pygmentize';