mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Merge branch 'master' into master-ios
This commit is contained in:
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
node-version: [16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ In a more narrow sense, particularly in media, blockchain is used to refer speci
|
||||
|
||||
Centralized networks are provided or controlled by a single entity. The examples are Threema, Signal, WhatsApp and Telegram. The advantage of that design is that the provider can innovate faster, and has a centralized approach to security. But the disadvantage is that the provider can change or discontinue the service, and leak, sell or disclose in some other way all users' data, including who they are connected with.
|
||||
|
||||
## Content padding
|
||||
## Content padding
|
||||
|
||||
[Message padding](#message-padding).
|
||||
|
||||
@@ -149,7 +149,7 @@ Generalizing [the definition](https://csrc.nist.gov/glossary/term/pairwise_pseud
|
||||
|
||||
In the context of SimpleX network, these are the identifiers generated by SMP relays to access anonymous messaging queues, with a separate identifier (and access credential) for each accessing party: recipient, sender and and optional notifications subscriber. The same approach is used by XFTP relays to access file chunks, with separate identifiers (and access credentials) for sender and each recipient.
|
||||
|
||||
## Peer-to-peer
|
||||
## Peer-to-peer
|
||||
|
||||
Peer-to-peer (P2P) is the network architecture when participants have equal rights and communicate directly via a general purpose transport or overlay network. Unlike client-server architecture, all peers in a P2P network both provide and consume the resources. In the context of messaging, P2P architecture usually means that the messages are sent between peers, without user accounts or messages being stored on any servers. Examples are Tox, Briar, Cwtch and many others.
|
||||
|
||||
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
---
|
||||
title: Hosting your own SMP Server
|
||||
revision: 31.01.2023
|
||||
revision: 05.06.2023
|
||||
---
|
||||
|
||||
| Updated 31.01.2023 | Languages: EN, [FR](/docs/lang/fr/SERVER.md), [CZ](/docs/lang/cs/SERVER.md) |
|
||||
| Updated 05.06.2023 | Languages: EN, [FR](/docs/lang/fr/SERVER.md), [CZ](/docs/lang/cs/SERVER.md) |
|
||||
|
||||
# Hosting your own SMP Server
|
||||
|
||||
## Overview
|
||||
|
||||
SMP server is the relay server used to pass messages in SimpleX network. SimpleX Chat apps have preset servers (for mobile apps these are smp8, smp9 and smp10.simplex.im), but you can easily change app configuration to use other servers.
|
||||
SMP server is the relay server used to pass messages in SimpleX network. SimpleX Chat apps have preset servers (for mobile apps these are smp11, smp12 and smp14.simplex.im), but you can easily change app configuration to use other servers.
|
||||
|
||||
SimpleX clients only determine which server is used to receive the messages, separately for each contact (or group connection with a group member), and these servers are only temporary, as the delivery address can change.
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: Hostování vlastního serveru SMP
|
||||
revision: 31.01.2023
|
||||
revision: 05.06.2023
|
||||
---
|
||||
| Aktualizováno 31.01.2023 | Jazyky: CZ, [EN](/docs/SERVER.md), [FR](/docs/lang/fr/SERVER.md) |
|
||||
| Aktualizováno 05.06.2023 | Jazyky: CZ, [EN](/docs/SERVER.md), [FR](/docs/lang/fr/SERVER.md) |
|
||||
|
||||
# Hostování vlastního serveru SMP
|
||||
|
||||
## Přehled
|
||||
|
||||
SMP server je relay server používaný k předávání zpráv v síti SimpleX. Aplikace SimpleX Chat mají přednastavené servery (pro mobilní aplikace jsou to smp8, smp9 a smp10.simplex.im), ale konfiguraci aplikace můžete snadno změnit a používat jiné servery.
|
||||
SMP server je relay server používaný k předávání zpráv v síti SimpleX. Aplikace SimpleX Chat mají přednastavené servery (pro mobilní aplikace jsou to smp11, smp12 a smp14.simplex.im), ale konfiguraci aplikace můžete snadno změnit a používat jiné servery.
|
||||
|
||||
Klienti SimpleX pouze určují, který server bude použit pro příjem zpráv, a to pro každý kontakt (nebo spojení skupiny s členem skupiny) zvlášť, přičemž tyto servery jsou pouze dočasné, protože adresa pro doručování se může změnit.
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: Héberger votre propre serveur SMP
|
||||
revision: 31.01.2023
|
||||
revision: 05.06.2023
|
||||
---
|
||||
| 31.01.2023 | FR, [EN](/docs/SERVER.md), [CZ](/docs/lang/cs/SERVER.md) |
|
||||
| 05.06.2023 | FR, [EN](/docs/SERVER.md), [CZ](/docs/lang/cs/SERVER.md) |
|
||||
|
||||
# Héberger votre propre serveur SMP
|
||||
|
||||
## Présentation générale
|
||||
|
||||
Un serveur SMP est un serveur relais utilisé pour transmettre les messages sur le réseau SimpleX. Les apps SimpleX Chat ont des serveurs prédéfinis (pour les apps mobiles, smp8, smp9 et smp10.simplex.im), mais vous pouvez facilement modifier la configuration de l'app pour utiliser d'autres serveurs.
|
||||
Un serveur SMP est un serveur relais utilisé pour transmettre les messages sur le réseau SimpleX. Les apps SimpleX Chat ont des serveurs prédéfinis (pour les apps mobiles, smp11, smp12 et smp14.simplex.im), mais vous pouvez facilement modifier la configuration de l'app pour utiliser d'autres serveurs.
|
||||
|
||||
Seuls les utilisateurs de SimpleX déterminent quel serveur est utilisé pour recevoir les messages, séparément pour chaque contact (ou pour chaque connexion à un membre d'un groupe), et ces serveurs ne sont que temporaires, car l'adresse de réception peut changer.
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 5.1.2.0
|
||||
version: 5.1.3.0
|
||||
#synopsis:
|
||||
#description:
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
|
||||
+2
-1
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 5.1.2.0
|
||||
version: 5.1.3.0
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
@@ -100,6 +100,7 @@ library
|
||||
Simplex.Chat.Migrations.M20230519_item_deleted_ts
|
||||
Simplex.Chat.Migrations.M20230526_indexes
|
||||
Simplex.Chat.Migrations.M20230529_indexes
|
||||
Simplex.Chat.Migrations.M20230608_deleted_contacts
|
||||
Simplex.Chat.Mobile
|
||||
Simplex.Chat.Mobile.WebRTC
|
||||
Simplex.Chat.Options
|
||||
|
||||
+23
-11
@@ -121,15 +121,16 @@ defaultChatConfig =
|
||||
testView = False,
|
||||
initialCleanupManagerDelay = 30 * 1000000, -- 30 seconds
|
||||
cleanupManagerInterval = 30 * 60, -- 30 minutes
|
||||
cleanupManagerStepDelay = 3 * 1000000, -- 3 seconds
|
||||
ciExpirationInterval = 30 * 60 * 1000000 -- 30 minutes
|
||||
}
|
||||
|
||||
_defaultSMPServers :: NonEmpty SMPServerWithAuth
|
||||
_defaultSMPServers =
|
||||
L.fromList
|
||||
[ "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion",
|
||||
"smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion",
|
||||
"smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion"
|
||||
[ "smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion",
|
||||
"smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion",
|
||||
"smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion"
|
||||
]
|
||||
|
||||
_defaultNtfServers :: [NtfServer]
|
||||
@@ -867,7 +868,7 @@ processChatCommand = \case
|
||||
Just _ -> pure []
|
||||
Nothing -> do
|
||||
conns <- withStore $ \db -> getContactConnections db userId ct
|
||||
withStore' (\db -> deleteContactWithoutGroups db user ct)
|
||||
withStore' (\db -> setContactDeleted db user ct)
|
||||
`catchError` (toView . CRChatError (Just user))
|
||||
pure $ map aConnId conns
|
||||
CTContactRequest -> pure $ chatCmdError (Just user) "not supported"
|
||||
@@ -2358,15 +2359,16 @@ cleanupManager :: forall m. ChatMonad m => m ()
|
||||
cleanupManager = do
|
||||
interval <- asks (cleanupManagerInterval . config)
|
||||
runWithoutInitialDelay interval
|
||||
delay <- asks (initialCleanupManagerDelay . config)
|
||||
liftIO $ threadDelay' delay
|
||||
initialDelay <- asks (initialCleanupManagerDelay . config)
|
||||
liftIO $ threadDelay' initialDelay
|
||||
stepDelay <- asks (cleanupManagerStepDelay . config)
|
||||
forever $ do
|
||||
flip catchError (toView . CRChatError Nothing) $ do
|
||||
waitChatStarted
|
||||
users <- withStoreCtx' (Just "cleanupManager, getUsers 1") getUsers
|
||||
let (us, us') = partition activeUser users
|
||||
forM_ us $ cleanupUser interval
|
||||
forM_ us' $ cleanupUser interval
|
||||
forM_ us $ cleanupUser interval stepDelay
|
||||
forM_ us' $ cleanupUser interval stepDelay
|
||||
cleanupMessages `catchError` (toView . CRChatError Nothing)
|
||||
liftIO $ threadDelay' $ diffToMicroseconds interval
|
||||
where
|
||||
@@ -2376,13 +2378,21 @@ cleanupManager = do
|
||||
let (us, us') = partition activeUser users
|
||||
forM_ us $ \u -> cleanupTimedItems cleanupInterval u `catchError` (toView . CRChatError (Just u))
|
||||
forM_ us' $ \u -> cleanupTimedItems cleanupInterval u `catchError` (toView . CRChatError (Just u))
|
||||
cleanupUser cleanupInterval user =
|
||||
cleanupUser cleanupInterval stepDelay user = do
|
||||
cleanupTimedItems cleanupInterval user `catchError` (toView . CRChatError (Just user))
|
||||
liftIO $ threadDelay' stepDelay
|
||||
cleanupDeletedContacts user `catchError` (toView . CRChatError (Just user))
|
||||
liftIO $ threadDelay' stepDelay
|
||||
cleanupTimedItems cleanupInterval user = do
|
||||
ts <- liftIO getCurrentTime
|
||||
let startTimedThreadCutoff = addUTCTime cleanupInterval ts
|
||||
timedItems <- withStoreCtx' (Just "cleanupManager, getTimedItems") $ \db -> getTimedItems db user startTimedThreadCutoff
|
||||
forM_ timedItems $ \(itemRef, deleteAt) -> startTimedItemThread user itemRef deleteAt `catchError` const (pure ())
|
||||
cleanupDeletedContacts user = do
|
||||
contacts <- withStore' (`getDeletedContacts` user)
|
||||
forM_ contacts $ \ct ->
|
||||
withStore' (\db -> deleteContactWithoutGroups db user ct)
|
||||
`catchError` (toView . CRChatError (Just user))
|
||||
cleanupMessages = do
|
||||
ts <- liftIO getCurrentTime
|
||||
let cutoffTs = addUTCTime (- (30 * nominalDay)) ts
|
||||
@@ -4295,7 +4305,9 @@ throwChatError = throwError . ChatError
|
||||
|
||||
deleteMembersConnections :: ChatMonad m => User -> [GroupMember] -> m ()
|
||||
deleteMembersConnections user members = do
|
||||
let memberConns = mapMaybe (\GroupMember {activeConn} -> activeConn) members
|
||||
let memberConns =
|
||||
filter (\Connection {connStatus} -> connStatus /= ConnDeleted) $
|
||||
mapMaybe (\GroupMember {activeConn} -> activeConn) members
|
||||
deleteAgentConnectionsAsync user $ map aConnId memberConns
|
||||
forM_ memberConns $ \conn -> withStore' $ \db -> updateConnectionStatus db conn ConnDeleted
|
||||
|
||||
@@ -4719,7 +4731,6 @@ withStoreCtx ctx_ action = do
|
||||
ChatController {chatStore} <- ask
|
||||
liftEitherError ChatErrorStore $ case ctx_ of
|
||||
Nothing -> withTransaction chatStore (runExceptT . action) `E.catch` handleInternal ""
|
||||
Just _ -> withTransaction chatStore (runExceptT . action) `E.catch` handleInternal ""
|
||||
-- uncomment to debug store performance
|
||||
-- Just ctx -> do
|
||||
-- t1 <- liftIO getCurrentTime
|
||||
@@ -4728,6 +4739,7 @@ withStoreCtx ctx_ action = do
|
||||
-- t2 <- liftIO getCurrentTime
|
||||
-- putStrLn $ "withStoreCtx end :: " <> show t2 <> " :: " <> ctx <> " :: duration=" <> show (diffToMilliseconds $ diffUTCTime t2 t1)
|
||||
-- pure r
|
||||
Just _ -> withTransaction chatStore (runExceptT . action) `E.catch` handleInternal ""
|
||||
where
|
||||
handleInternal :: String -> E.SomeException -> IO (Either StoreError a)
|
||||
handleInternal ctxStr e = pure . Left . SEInternalError $ show e <> ctxStr
|
||||
|
||||
@@ -112,6 +112,7 @@ data ChatConfig = ChatConfig
|
||||
testView :: Bool,
|
||||
initialCleanupManagerDelay :: Int64,
|
||||
cleanupManagerInterval :: NominalDiffTime,
|
||||
cleanupManagerStepDelay :: Int64,
|
||||
ciExpirationInterval :: Int64 -- microseconds
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Migrations.M20230608_deleted_contacts where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20230608_deleted_contacts :: Query
|
||||
m20230608_deleted_contacts =
|
||||
[sql|
|
||||
ALTER TABLE contacts ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX msg_delivery_events_msg_delivery_id ON msg_delivery_events(msg_delivery_id);
|
||||
|]
|
||||
|
||||
down_m20230608_deleted_contacts :: Query
|
||||
down_m20230608_deleted_contacts =
|
||||
[sql|
|
||||
DROP INDEX msg_delivery_events_msg_delivery_id;
|
||||
|
||||
ALTER TABLE contacts DROP COLUMN deleted;
|
||||
|]
|
||||
@@ -63,6 +63,7 @@ CREATE TABLE contacts(
|
||||
contact_used INTEGER DEFAULT 0 CHECK(contact_used NOT NULL),
|
||||
user_preferences TEXT DEFAULT '{}' CHECK(user_preferences NOT NULL),
|
||||
chat_ts TEXT,
|
||||
deleted INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY(user_id, local_display_name)
|
||||
REFERENCES display_names(user_id, local_display_name)
|
||||
ON DELETE CASCADE
|
||||
@@ -652,3 +653,6 @@ CREATE INDEX idx_msg_deliveries_agent_ack_cmd_id ON msg_deliveries(
|
||||
connection_id,
|
||||
agent_ack_cmd_id
|
||||
);
|
||||
CREATE INDEX msg_delivery_events_msg_delivery_id ON msg_delivery_events(
|
||||
msg_delivery_id
|
||||
);
|
||||
|
||||
+42
-19
@@ -52,6 +52,8 @@ module Simplex.Chat.Store
|
||||
deleteContactConnectionsAndFiles,
|
||||
deleteContact,
|
||||
deleteContactWithoutGroups,
|
||||
setContactDeleted,
|
||||
getDeletedContacts,
|
||||
getContactByName,
|
||||
getContact,
|
||||
getContactIdByName,
|
||||
@@ -395,6 +397,7 @@ import Simplex.Chat.Migrations.M20230511_reactions
|
||||
import Simplex.Chat.Migrations.M20230519_item_deleted_ts
|
||||
import Simplex.Chat.Migrations.M20230526_indexes
|
||||
import Simplex.Chat.Migrations.M20230529_indexes
|
||||
import Simplex.Chat.Migrations.M20230608_deleted_contacts
|
||||
import Simplex.Chat.Protocol
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Util (week)
|
||||
@@ -476,7 +479,8 @@ schemaMigrations =
|
||||
("20230511_reactions", m20230511_reactions, Just down_m20230511_reactions),
|
||||
("20230519_item_deleted_ts", m20230519_item_deleted_ts, Just down_m20230519_item_deleted_ts),
|
||||
("20230526_indexes", m20230526_indexes, Just down_m20230526_indexes),
|
||||
("20230529_indexes", m20230529_indexes, Just down_m20230529_indexes)
|
||||
("20230529_indexes", m20230529_indexes, Just down_m20230529_indexes),
|
||||
("20230608_deleted_contacts", m20230608_deleted_contacts, Just down_m20230608_deleted_contacts)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
@@ -547,7 +551,7 @@ getUsersInfo db = getUsers db >>= mapM getUserInfo
|
||||
SELECT COUNT(1)
|
||||
FROM chat_items i
|
||||
JOIN contacts ct USING (contact_id)
|
||||
WHERE i.user_id = ? AND i.item_status = ? AND (ct.enable_ntfs = 1 OR ct.enable_ntfs IS NULL)
|
||||
WHERE i.user_id = ? AND i.item_status = ? AND (ct.enable_ntfs = 1 OR ct.enable_ntfs IS NULL) AND ct.deleted = 0
|
||||
|]
|
||||
(userId, CISRcvNew)
|
||||
gCount <-
|
||||
@@ -622,7 +626,7 @@ getUserByARcvFileId db aRcvFileId =
|
||||
getUserByContactId :: DB.Connection -> ContactId -> ExceptT StoreError IO User
|
||||
getUserByContactId db contactId =
|
||||
ExceptT . firstRow toUser (SEUserNotFoundByContactId contactId) $
|
||||
DB.query db (userQuery <> " JOIN contacts ct ON ct.user_id = u.user_id WHERE ct.contact_id = ?") (Only contactId)
|
||||
DB.query db (userQuery <> " JOIN contacts ct ON ct.user_id = u.user_id WHERE ct.contact_id = ? AND ct.deleted = 0") (Only contactId)
|
||||
|
||||
getUserByGroupId :: DB.Connection -> GroupId -> ExceptT StoreError IO User
|
||||
getUserByGroupId db groupId =
|
||||
@@ -711,7 +715,7 @@ getConnReqContactXContactId db user@User {userId} cReqHash = do
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||
JOIN connections c ON c.contact_id = ct.contact_id
|
||||
WHERE ct.user_id = ? AND c.via_contact_uri_hash = ?
|
||||
WHERE ct.user_id = ? AND c.via_contact_uri_hash = ? AND ct.deleted = 0
|
||||
ORDER BY c.connection_id DESC
|
||||
LIMIT 1
|
||||
|]
|
||||
@@ -757,7 +761,6 @@ getProfileById db userId profileId =
|
||||
[sql|
|
||||
SELECT cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, cp.preferences -- , ct.user_preferences
|
||||
FROM contact_profiles cp
|
||||
-- JOIN contacts ct ON cp.contact_profile_id = ct.contact_profile_id
|
||||
WHERE cp.user_id = ? AND cp.contact_profile_id = ?
|
||||
|]
|
||||
(userId, profileId)
|
||||
@@ -849,6 +852,19 @@ deleteContactWithoutGroups db user@User {userId} Contact {contactId, localDispla
|
||||
DB.execute db "DELETE FROM contacts WHERE user_id = ? AND contact_id = ?" (userId, contactId)
|
||||
forM_ customUserProfileId $ \profileId -> deleteUnusedIncognitoProfileById_ db user profileId
|
||||
|
||||
setContactDeleted :: DB.Connection -> User -> Contact -> IO ()
|
||||
setContactDeleted db User {userId} Contact {contactId} = do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute db "UPDATE contacts SET deleted = 1, updated_at = ? WHERE user_id = ? AND contact_id = ?" (currentTs, userId, contactId)
|
||||
|
||||
getDeletedContacts :: DB.Connection -> User -> IO [Contact]
|
||||
getDeletedContacts db user@User {userId} = do
|
||||
contactIds <- map fromOnly <$> DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND deleted = 1" (Only userId)
|
||||
rights <$> mapM (runExceptT . getDeletedContact db user) contactIds
|
||||
|
||||
getDeletedContact :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO Contact
|
||||
getDeletedContact db user contactId = getContact_ db user contactId True
|
||||
|
||||
deleteUnusedIncognitoProfileById_ :: DB.Connection -> User -> ProfileId -> IO ()
|
||||
deleteUnusedIncognitoProfileById_ db User {userId} profile_id =
|
||||
DB.executeNamed
|
||||
@@ -1061,7 +1077,7 @@ getContactByName db user localDisplayName = do
|
||||
|
||||
getUserContacts :: DB.Connection -> User -> IO [Contact]
|
||||
getUserContacts db user@User {userId} = do
|
||||
contactIds <- map fromOnly <$> DB.query db "SELECT contact_id FROM contacts WHERE user_id = ?" (Only userId)
|
||||
contactIds <- map fromOnly <$> DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND deleted = 0" (Only userId)
|
||||
rights <$> mapM (runExceptT . getContact db user) contactIds
|
||||
|
||||
-- only used in tests
|
||||
@@ -1365,7 +1381,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
||||
WHERE ct.user_id = ? AND ct.xcontact_id = ?
|
||||
WHERE ct.user_id = ? AND ct.xcontact_id = ? AND ct.deleted = 0
|
||||
ORDER BY c.connection_id DESC
|
||||
LIMIT 1
|
||||
|]
|
||||
@@ -1615,6 +1631,7 @@ getMatchingContacts db user@User {userId} Contact {contactId, profile = LocalPro
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles p ON ct.contact_profile_id = p.contact_profile_id
|
||||
WHERE ct.user_id = ? AND ct.contact_id != ?
|
||||
AND ct.deleted = 0
|
||||
AND p.display_name = ? AND p.full_name = ?
|
||||
AND ((p.image IS NULL AND ? IS NULL) OR p.image = ?)
|
||||
|]
|
||||
@@ -1657,7 +1674,7 @@ matchReceivedProbe db user@User {userId} _from@Contact {contactId} (Probe probe)
|
||||
SELECT c.contact_id
|
||||
FROM contacts c
|
||||
JOIN received_probes r ON r.contact_id = c.contact_id
|
||||
WHERE c.user_id = ? AND r.probe_hash = ? AND r.probe IS NULL
|
||||
WHERE c.user_id = ? AND c.deleted = 0 AND r.probe_hash = ? AND r.probe IS NULL
|
||||
|]
|
||||
(userId, probeHash)
|
||||
currentTs <- getCurrentTime
|
||||
@@ -1678,7 +1695,7 @@ matchReceivedProbeHash db user@User {userId} _from@Contact {contactId} (ProbeHas
|
||||
SELECT c.contact_id, r.probe
|
||||
FROM contacts c
|
||||
JOIN received_probes r ON r.contact_id = c.contact_id
|
||||
WHERE c.user_id = ? AND r.probe_hash = ? AND r.probe IS NOT NULL
|
||||
WHERE c.user_id = ? AND c.deleted = 0 AND r.probe_hash = ? AND r.probe IS NOT NULL
|
||||
|]
|
||||
(userId, probeHash)
|
||||
currentTs <- getCurrentTime
|
||||
@@ -1703,7 +1720,7 @@ matchSentProbe db user@User {userId} _from@Contact {contactId} (Probe probe) = d
|
||||
FROM contacts c
|
||||
JOIN sent_probes s ON s.contact_id = c.contact_id
|
||||
JOIN sent_probe_hashes h ON h.sent_probe_id = s.sent_probe_id
|
||||
WHERE c.user_id = ? AND s.probe = ? AND h.contact_id = ?
|
||||
WHERE c.user_id = ? AND c.deleted = 0 AND s.probe = ? AND h.contact_id = ?
|
||||
|]
|
||||
(userId, probe, contactId)
|
||||
case contactIds of
|
||||
@@ -1808,7 +1825,7 @@ getConnectionEntity db user@User {userId, userContactId} agentConnId = do
|
||||
p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts
|
||||
FROM contacts c
|
||||
JOIN contact_profiles p ON c.contact_profile_id = p.contact_profile_id
|
||||
WHERE c.user_id = ? AND c.contact_id = ?
|
||||
WHERE c.user_id = ? AND c.contact_id = ? AND c.deleted = 0
|
||||
|]
|
||||
(userId, contactId)
|
||||
toContact' :: Int64 -> Connection -> [(ProfileId, ContactName, Text, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime)] -> Either StoreError Contact
|
||||
@@ -1913,6 +1930,7 @@ getConnectionsContacts db agentConnIds = do
|
||||
JOIN connections c ON c.contact_id = ct.contact_id
|
||||
WHERE c.agent_conn_id IN (SELECT conn_id FROM temp.conn_ids)
|
||||
AND c.conn_type = ?
|
||||
AND ct.deleted = 0
|
||||
|]
|
||||
(Only ConnContact)
|
||||
DB.execute_ db "DROP TABLE temp.conn_ids"
|
||||
@@ -2388,7 +2406,7 @@ getContactViaMember db user@User {userId} GroupMember {groupMemberId} =
|
||||
where cc.contact_id = ct.contact_id
|
||||
)
|
||||
JOIN group_members m ON m.contact_id = ct.contact_id
|
||||
WHERE ct.user_id = ? AND m.group_member_id = ?
|
||||
WHERE ct.user_id = ? AND m.group_member_id = ? AND ct.deleted = 0
|
||||
|]
|
||||
(userId, groupMemberId)
|
||||
|
||||
@@ -2697,7 +2715,7 @@ getViaGroupMember db User {userId, userContactId} Contact {contactId} =
|
||||
FROM connections cc
|
||||
where cc.group_member_id = m.group_member_id
|
||||
)
|
||||
WHERE ct.user_id = ? AND ct.contact_id = ? AND mu.contact_id = ?
|
||||
WHERE ct.user_id = ? AND ct.contact_id = ? AND mu.contact_id = ? AND ct.deleted = 0
|
||||
|]
|
||||
(userId, contactId, userContactId)
|
||||
where
|
||||
@@ -2727,7 +2745,7 @@ getViaGroupContact db user@User {userId} GroupMember {groupMemberId} =
|
||||
)
|
||||
JOIN groups g ON g.group_id = ct.via_group
|
||||
JOIN group_members m ON m.group_id = g.group_id AND m.contact_id = ct.contact_id
|
||||
WHERE ct.user_id = ? AND m.group_member_id = ?
|
||||
WHERE ct.user_id = ? AND m.group_member_id = ? AND ct.deleted = 0
|
||||
|]
|
||||
(userId, groupMemberId)
|
||||
where
|
||||
@@ -3826,6 +3844,7 @@ getDirectChatPreviews_ db user@User {userId} = do
|
||||
LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id
|
||||
WHERE ct.user_id = ?
|
||||
AND ((c.conn_level = 0 AND c.via_group_link = 0) OR ct.contact_used = 1)
|
||||
AND ct.deleted = 0
|
||||
AND c.connection_id = (
|
||||
SELECT cc_connection_id FROM (
|
||||
SELECT
|
||||
@@ -4107,10 +4126,13 @@ getDirectChatBefore_ db User {userId} ct@Contact {contactId} beforeChatItemId co
|
||||
getContactIdByName :: DB.Connection -> User -> ContactName -> ExceptT StoreError IO Int64
|
||||
getContactIdByName db User {userId} cName =
|
||||
ExceptT . firstRow fromOnly (SEContactNotFoundByName cName) $
|
||||
DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND local_display_name = ?" (userId, cName)
|
||||
DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND local_display_name = ? AND deleted = 0" (userId, cName)
|
||||
|
||||
getContact :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO Contact
|
||||
getContact db user@User {userId} contactId =
|
||||
getContact db user contactId = getContact_ db user contactId False
|
||||
|
||||
getContact_ :: DB.Connection -> User -> Int64 -> Bool -> ExceptT StoreError IO Contact
|
||||
getContact_ db user@User {userId} contactId deleted =
|
||||
ExceptT . fmap join . firstRow (toContactOrError user) (SEContactNotFound contactId) $
|
||||
DB.query
|
||||
db
|
||||
@@ -4126,6 +4148,7 @@ getContact db user@User {userId} contactId =
|
||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
||||
WHERE ct.user_id = ? AND ct.contact_id = ?
|
||||
AND ct.deleted = ?
|
||||
AND c.connection_id = (
|
||||
SELECT cc_connection_id FROM (
|
||||
SELECT
|
||||
@@ -4138,7 +4161,7 @@ getContact db user@User {userId} contactId =
|
||||
)
|
||||
)
|
||||
|]
|
||||
(userId, contactId, ConnReady, ConnSndReady)
|
||||
(userId, contactId, deleted, ConnReady, ConnSndReady)
|
||||
|
||||
getGroupChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTGroup)
|
||||
getGroupChat db user groupId pagination search_ = do
|
||||
@@ -5377,7 +5400,7 @@ getXGrpMemIntroContDirect db User {userId} Contact {contactId} = do
|
||||
FROM connections cc
|
||||
where cc.group_member_id = mh.group_member_id
|
||||
)
|
||||
WHERE ct.user_id = ? AND ct.contact_id = ? AND mh.member_category = ?
|
||||
WHERE ct.user_id = ? AND ct.contact_id = ? AND ct.deleted = 0 AND mh.member_category = ?
|
||||
|]
|
||||
(userId, contactId, GCHostMember)
|
||||
where
|
||||
@@ -5407,7 +5430,7 @@ getXGrpMemIntroContGroup db User {userId} GroupMember {groupMemberId} = do
|
||||
FROM connections cc
|
||||
where cc.group_member_id = mh.group_member_id
|
||||
)
|
||||
WHERE m.user_id = ? AND m.group_member_id = ? AND mh.member_category = ?
|
||||
WHERE m.user_id = ? AND m.group_member_id = ? AND mh.member_category = ? AND ct.deleted = 0
|
||||
|]
|
||||
(userId, groupMemberId, GCHostMember)
|
||||
where
|
||||
|
||||
@@ -1327,7 +1327,7 @@ testUsersDifferentCIExpirationTTL tmp = do
|
||||
|
||||
alice #$> ("/_get chat @4 count=100", chat, [])
|
||||
where
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, ciExpirationInterval = 500000}
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, cleanupManagerStepDelay = 0, ciExpirationInterval = 500000}
|
||||
|
||||
testUsersRestartCIExpiration :: HasCallStack => FilePath -> IO ()
|
||||
testUsersRestartCIExpiration tmp = do
|
||||
@@ -1410,7 +1410,7 @@ testUsersRestartCIExpiration tmp = do
|
||||
|
||||
alice #$> ("/_get chat @4 count=100", chat, [])
|
||||
where
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, ciExpirationInterval = 500000}
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, cleanupManagerStepDelay = 0, ciExpirationInterval = 500000}
|
||||
|
||||
testEnableCIExpirationOnlyForOneUser :: HasCallStack => FilePath -> IO ()
|
||||
testEnableCIExpirationOnlyForOneUser tmp = do
|
||||
@@ -1481,7 +1481,7 @@ testEnableCIExpirationOnlyForOneUser tmp = do
|
||||
-- new messages are not deleted for second user
|
||||
alice #$> ("/_get chat @4 count=100", chat, chatFeatures <> [(1, "alisa 1"), (0, "alisa 2"), (1, "alisa 3"), (0, "alisa 4"), (1, "alisa 5"), (0, "alisa 6")])
|
||||
where
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, ciExpirationInterval = 500000}
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, cleanupManagerStepDelay = 0, ciExpirationInterval = 500000}
|
||||
|
||||
testDisableCIExpirationOnlyForOneUser :: HasCallStack => FilePath -> IO ()
|
||||
testDisableCIExpirationOnlyForOneUser tmp = do
|
||||
@@ -1539,7 +1539,7 @@ testDisableCIExpirationOnlyForOneUser tmp = do
|
||||
-- second user messages are deleted
|
||||
alice #$> ("/_get chat @4 count=100", chat, [])
|
||||
where
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, ciExpirationInterval = 500000}
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, cleanupManagerStepDelay = 0, ciExpirationInterval = 500000}
|
||||
|
||||
testUsersTimedMessages :: HasCallStack => FilePath -> IO ()
|
||||
testUsersTimedMessages tmp = do
|
||||
|
||||
@@ -9,6 +9,7 @@ import Control.Concurrent (threadDelay)
|
||||
import Control.Concurrent.Async (concurrently_)
|
||||
import Control.Monad (when)
|
||||
import qualified Data.Text as T
|
||||
import Simplex.Chat.Controller (ChatConfig (..))
|
||||
import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
|
||||
import Simplex.Chat.Types (GroupMemberRole (..))
|
||||
import System.Directory (copyFile)
|
||||
@@ -420,7 +421,7 @@ testGroup2 =
|
||||
|
||||
testGroupDelete :: HasCallStack => FilePath -> IO ()
|
||||
testGroupDelete =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
testChatCfg3 cfg aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> do
|
||||
createGroup3 "team" alice bob cath
|
||||
alice ##> "/d #team"
|
||||
@@ -444,12 +445,15 @@ testGroupDelete =
|
||||
alice <##> bob
|
||||
alice <##> cath
|
||||
-- unused group contacts are deleted
|
||||
threadDelay 3000000
|
||||
bob ##> "@cath hi"
|
||||
bob <## "no contact cath"
|
||||
(cath </)
|
||||
cath ##> "@bob hi"
|
||||
cath <## "no contact bob"
|
||||
(bob </)
|
||||
where
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, cleanupManagerInterval = 1, cleanupManagerStepDelay = 0}
|
||||
|
||||
testGroupSameName :: HasCallStack => FilePath -> IO ()
|
||||
testGroupSameName =
|
||||
@@ -1151,7 +1155,7 @@ testUpdateMemberRole =
|
||||
|
||||
testGroupDeleteUnusedContacts :: HasCallStack => FilePath -> IO ()
|
||||
testGroupDeleteUnusedContacts =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
testChatCfg3 cfg aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> do
|
||||
-- create group 1
|
||||
createGroup3 "team" alice bob cath
|
||||
@@ -1210,6 +1214,7 @@ testGroupDeleteUnusedContacts =
|
||||
cath `hasContactProfiles` ["alice", "bob", "cath"]
|
||||
-- delete group 2, unused contacts and profiles are deleted
|
||||
deleteGroup alice bob cath "club"
|
||||
threadDelay 3000000
|
||||
bob ##> "/contacts"
|
||||
bob <## "alice (Alice)"
|
||||
bob `hasContactProfiles` ["alice", "bob"]
|
||||
@@ -1217,6 +1222,7 @@ testGroupDeleteUnusedContacts =
|
||||
cath <## "alice (Alice)"
|
||||
cath `hasContactProfiles` ["alice", "cath"]
|
||||
where
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, cleanupManagerInterval = 1, cleanupManagerStepDelay = 0}
|
||||
deleteGroup :: HasCallStack => TestCC -> TestCC -> TestCC -> String -> IO ()
|
||||
deleteGroup alice bob cath group = do
|
||||
alice ##> ("/d #" <> group)
|
||||
@@ -1827,7 +1833,7 @@ testGroupLinkIncognitoMembership =
|
||||
|
||||
testGroupLinkUnusedHostContactDeleted :: HasCallStack => FilePath -> IO ()
|
||||
testGroupLinkUnusedHostContactDeleted =
|
||||
testChat2 aliceProfile bobProfile $
|
||||
testChatCfg2 cfg aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
-- create group 1
|
||||
alice ##> "/g team"
|
||||
@@ -1881,10 +1887,12 @@ testGroupLinkUnusedHostContactDeleted =
|
||||
bob `hasContactProfiles` ["alice", "bob"]
|
||||
-- delete group 2, unused host contact and profile are deleted
|
||||
bobLeaveDeleteGroup alice bob "club"
|
||||
threadDelay 3000000
|
||||
bob ##> "/contacts"
|
||||
(bob </)
|
||||
bob `hasContactProfiles` ["bob"]
|
||||
where
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, cleanupManagerInterval = 1, cleanupManagerStepDelay = 0}
|
||||
bobLeaveDeleteGroup :: HasCallStack => TestCC -> TestCC -> String -> IO ()
|
||||
bobLeaveDeleteGroup alice bob group = do
|
||||
bob ##> ("/l " <> group)
|
||||
@@ -1899,7 +1907,7 @@ testGroupLinkUnusedHostContactDeleted =
|
||||
|
||||
testGroupLinkIncognitoUnusedHostContactsDeleted :: HasCallStack => FilePath -> IO ()
|
||||
testGroupLinkIncognitoUnusedHostContactsDeleted =
|
||||
testChat2 aliceProfile bobProfile $
|
||||
testChatCfg2 cfg aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
bob #$> ("/incognito on", id, "ok")
|
||||
bobIncognitoTeam <- createGroupBobIncognito alice bob "team" "alice"
|
||||
@@ -1912,15 +1920,18 @@ testGroupLinkIncognitoUnusedHostContactsDeleted =
|
||||
bob `hasContactProfiles` ["alice", "alice", "bob", T.pack bobIncognitoTeam, T.pack bobIncognitoClub]
|
||||
-- delete group 1, unused host contact and profile are deleted
|
||||
bobLeaveDeleteGroup alice bob "team" bobIncognitoTeam
|
||||
threadDelay 3000000
|
||||
bob ##> "/contacts"
|
||||
bob <## "i alice_1 (Alice)"
|
||||
bob `hasContactProfiles` ["alice", "bob", T.pack bobIncognitoClub]
|
||||
-- delete group 2, unused host contact and profile are deleted
|
||||
bobLeaveDeleteGroup alice bob "club" bobIncognitoClub
|
||||
threadDelay 3000000
|
||||
bob ##> "/contacts"
|
||||
(bob </)
|
||||
bob `hasContactProfiles` ["bob"]
|
||||
where
|
||||
cfg = testCfg {initialCleanupManagerDelay = 0, cleanupManagerInterval = 1, cleanupManagerStepDelay = 0}
|
||||
createGroupBobIncognito :: HasCallStack => TestCC -> TestCC -> String -> String -> IO String
|
||||
createGroupBobIncognito alice bob group bobsAliceContact = do
|
||||
alice ##> ("/g " <> group)
|
||||
|
||||
+139
-6
@@ -3,10 +3,47 @@ const markdownItAnchor = require("markdown-it-anchor")
|
||||
const markdownItReplaceLink = require('markdown-it-replace-link')
|
||||
const slugify = require("slugify")
|
||||
const uri = require('fast-uri')
|
||||
const i18n = require('eleventy-plugin-i18n');
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const pluginRss = require('@11ty/eleventy-plugin-rss');
|
||||
const i18n = require('eleventy-plugin-i18n')
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
const pluginRss = require('@11ty/eleventy-plugin-rss')
|
||||
const { JSDOM } = require('jsdom')
|
||||
|
||||
|
||||
// The implementation of Glossary feature
|
||||
const md = new markdownIt()
|
||||
const glossaryMarkdownContent = fs.readFileSync(path.resolve(__dirname, '../docs/GLOSSARY.md'), 'utf8')
|
||||
const glossaryHtmlContent = md.render(glossaryMarkdownContent)
|
||||
const glossaryDOM = new JSDOM(glossaryHtmlContent)
|
||||
const glossaryDocument = glossaryDOM.window.document
|
||||
const glossary = require('./src/_data/glossary.json')
|
||||
|
||||
glossary.forEach(item => {
|
||||
const headers = Array.from(glossaryDocument.querySelectorAll("h2"))
|
||||
const matchingHeader = headers.find(header => header.textContent.trim() === item.definition)
|
||||
|
||||
if (matchingHeader) {
|
||||
let sibling = matchingHeader.nextElementSibling
|
||||
let definition = ''
|
||||
let firstParagraph = ''
|
||||
let paragraphCount = 0
|
||||
|
||||
while (sibling && sibling.tagName !== 'H2') {
|
||||
if (sibling.tagName === 'P') {
|
||||
paragraphCount += 1
|
||||
if (firstParagraph === '') {
|
||||
firstParagraph = sibling.innerHTML
|
||||
}
|
||||
}
|
||||
definition += sibling.outerHTML || sibling.textContent
|
||||
sibling = sibling.nextElementSibling
|
||||
}
|
||||
|
||||
item.definition = definition
|
||||
item.tooltip = firstParagraph
|
||||
item.hasMultipleParagraphs = paragraphCount > 1
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const globalConfig = {
|
||||
@@ -55,6 +92,102 @@ module.exports = function (ty) {
|
||||
}
|
||||
})
|
||||
|
||||
ty.addFilter('applyGlossary', function (content) {
|
||||
const dom = new JSDOM(content)
|
||||
const { document } = dom.window
|
||||
const body = document.querySelector('body')
|
||||
const allContentNodes = document.querySelectorAll('p, td, a, h1, h2, h3, h4')
|
||||
const overlayIds = []
|
||||
|
||||
glossary.forEach((term, index) => {
|
||||
let changeNoted = false
|
||||
const id = term.term.toLowerCase().replace(/\s/g, '-')
|
||||
|
||||
allContentNodes.forEach((node) => {
|
||||
const regex = new RegExp(`(?<![/#])\\b${term.term}\\b`, 'gi')
|
||||
const replacement = `<span data-glossary="tooltip-${id}" class="glossary-term">${term.term}</span>`
|
||||
const beforeContent = node.innerHTML
|
||||
node.innerHTML = node.innerHTML.replace(regex, replacement)
|
||||
if (beforeContent !== node.innerHTML && !changeNoted) {
|
||||
changeNoted = true
|
||||
}
|
||||
})
|
||||
|
||||
if (changeNoted) {
|
||||
const definitionTooltipDiv = document.createElement('div')
|
||||
definitionTooltipDiv.id = `tooltip-${id}`
|
||||
definitionTooltipDiv.className = "glossary-tooltip"
|
||||
const titleH4 = document.createElement('h4')
|
||||
titleH4.innerHTML = term.term
|
||||
titleH4.className = "tooltip-title"
|
||||
const p = document.createElement('p')
|
||||
p.innerHTML = term.tooltip
|
||||
const innerDiv = document.createElement('div')
|
||||
innerDiv.appendChild(titleH4)
|
||||
innerDiv.appendChild(p)
|
||||
if (term.hasMultipleParagraphs) {
|
||||
const readMoreBtn = document.createElement('button')
|
||||
readMoreBtn.innerHTML = "Read more"
|
||||
readMoreBtn.className = "read-more-btn open-overlay-btn"
|
||||
readMoreBtn.setAttribute('data-show-overlay', id)
|
||||
innerDiv.appendChild(readMoreBtn)
|
||||
}
|
||||
innerDiv.className = "tooltip-content"
|
||||
definitionTooltipDiv.appendChild(innerDiv)
|
||||
body.appendChild(definitionTooltipDiv)
|
||||
}
|
||||
|
||||
let tooltipDom = new JSDOM(term.definition)
|
||||
let tooltipDocument = tooltipDom.window.document
|
||||
const hashList = [term.term.toLowerCase().replace(/\s/g, '-')]
|
||||
tooltipDocument.querySelectorAll('a[href*="#"]').forEach(a => {
|
||||
let hashIndex = a.href.indexOf("#")
|
||||
if (hashIndex !== -1) {
|
||||
let hash = a.href.substring(hashIndex + 1)
|
||||
hashList.push(hash)
|
||||
}
|
||||
})
|
||||
|
||||
hashList.forEach(hash => {
|
||||
if (!overlayIds.includes(hash)) {
|
||||
let termFromHash = glossary.find(term => term.term.toLowerCase().replace(/\s/g, '-') === hash)
|
||||
if (!termFromHash) return
|
||||
|
||||
const overlayDiv = document.createElement('div')
|
||||
overlayDiv.id = hash
|
||||
overlayDiv.className = "overlay glossary-overlay hidden"
|
||||
const overlayCardDiv = document.createElement('div')
|
||||
overlayCardDiv.className = "overlay-card"
|
||||
const overlayTitleH1 = document.createElement('h1')
|
||||
overlayTitleH1.className = "overlay-title"
|
||||
overlayTitleH1.innerHTML = termFromHash.term
|
||||
const overlayContent = document.createElement('div')
|
||||
overlayContent.className = "overlay-content"
|
||||
overlayContent.innerHTML = termFromHash.definition
|
||||
const crossSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg")
|
||||
crossSVG.setAttribute('class', 'close-overlay-btn')
|
||||
crossSVG.setAttribute('id', 'cross')
|
||||
crossSVG.setAttribute('width', '16')
|
||||
crossSVG.setAttribute('height', '16')
|
||||
crossSVG.setAttribute('viewBox', '0 0 13 13')
|
||||
crossSVG.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
|
||||
const crossPath = document.createElementNS("http://www.w3.org/2000/svg", "path")
|
||||
crossPath.setAttribute('d', 'M12.7973 11.5525L7.59762 6.49833L12.7947 1.44675C13.055 1.19371 13.0658 0.771991 12.8188 0.505331C12.5718 0.238674 12.1602 0.227644 11.8999 0.480681L6.65343 5.58028L1.09979 0.182228C0.805 0.002228 0.430001 0.002228 0.135211 0.182228C-0.159579 0.362228 -0.159579 0.697228 0.135211 0.877228L5.68885 6.27528L0.4918 11.3295C0.231501 11.5825 0.220703 12.0042 0.467664 12.2709C0.714625 12.5376 1.12625 12.5486 1.38655 12.2956L6.63302 7.196L12.1867 12.5941C12.4815 12.7741 12.8565 12.7741 13.1513 12.5941C13.4461 12.4141 13.4461 12.0791 13.1513 11.8991L12.7973 11.5525Z')
|
||||
crossSVG.appendChild(crossPath)
|
||||
|
||||
overlayCardDiv.appendChild(overlayTitleH1)
|
||||
overlayCardDiv.appendChild(overlayContent)
|
||||
overlayCardDiv.appendChild(crossSVG)
|
||||
overlayDiv.appendChild(overlayCardDiv)
|
||||
body.appendChild(overlayDiv)
|
||||
overlayIds.push(hash)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return dom.serialize()
|
||||
})
|
||||
|
||||
ty.addShortcode("completeRoute", (obj) => {
|
||||
const urlParts = obj.url.split("/")
|
||||
|
||||
@@ -88,7 +221,7 @@ module.exports = function (ty) {
|
||||
}
|
||||
})
|
||||
|
||||
ty.addPlugin(pluginRss);
|
||||
ty.addPlugin(pluginRss)
|
||||
|
||||
ty.addPlugin(i18n, {
|
||||
translations,
|
||||
@@ -139,7 +272,7 @@ module.exports = function (ty) {
|
||||
const url = doc.url.replace("/docs/", "")
|
||||
const urlParts = url.split("/")
|
||||
|
||||
if (doc.inputPath.includes(referenceSubmenu)) {
|
||||
if (doc.inputPath.split('/').includes(referenceSubmenu)) {
|
||||
if (urlParts.length === 1 && urlParts[0] !== "") {
|
||||
const index = newDocs.findIndex((ele) => ele.lang === 'en' && ele.menu === referenceMenu.menu)
|
||||
if (index !== -1) {
|
||||
|
||||
@@ -231,5 +231,6 @@
|
||||
"click-to-see": "Click to see",
|
||||
"menu": "Menu",
|
||||
"on-this-page": "On this page",
|
||||
"back-to-top": "Back to top"
|
||||
"back-to-top": "Back to top",
|
||||
"glossary": "Glossary"
|
||||
}
|
||||
@@ -29,6 +29,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"eleventy-plugin-i18n": "^0.1.3",
|
||||
"gray-matter": "^4.0.3"
|
||||
"fs": "^0.0.1-security",
|
||||
"gray-matter": "^4.0.3",
|
||||
"jsdom": "^22.1.0",
|
||||
"markdown-it": "^13.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
[
|
||||
{
|
||||
"term": "Address portability",
|
||||
"definition": "Address portability"
|
||||
},
|
||||
{
|
||||
"term": "Anonymous credentials",
|
||||
"definition": "Anonymous credentials"
|
||||
},
|
||||
{
|
||||
"term": "Blockchain",
|
||||
"definition": "Blockchain"
|
||||
},
|
||||
{
|
||||
"term": "Break-in recovery",
|
||||
"definition": "Post-compromise security"
|
||||
},
|
||||
{
|
||||
"term": "Centralized network",
|
||||
"definition": "Centralized network"
|
||||
},
|
||||
{
|
||||
"term": "Content padding",
|
||||
"definition": "Message padding"
|
||||
},
|
||||
{
|
||||
"term": "Decentralized network",
|
||||
"definition": "Decentralized network"
|
||||
},
|
||||
{
|
||||
"term": "Defense in depth",
|
||||
"definition": "Defense in depth"
|
||||
},
|
||||
{
|
||||
"term": "Double ratchet algorithm",
|
||||
"definition": "Double ratchet algorithm"
|
||||
},
|
||||
{
|
||||
"term": "End-to-end encryption",
|
||||
"definition": "End-to-end encryption"
|
||||
},
|
||||
{
|
||||
"term": "Federated network",
|
||||
"definition": "Federated network"
|
||||
},
|
||||
{
|
||||
"term": "Forward secrecy",
|
||||
"definition": "Forward secrecy"
|
||||
},
|
||||
{
|
||||
"term": "Key agreement protocol",
|
||||
"definition": "Key agreement protocol"
|
||||
},
|
||||
{
|
||||
"term": "Key exchange",
|
||||
"definition": "Key agreement protocol"
|
||||
},
|
||||
{
|
||||
"term": "Man-in-the-middle attack",
|
||||
"definition": "Man-in-the-middle attack"
|
||||
},
|
||||
{
|
||||
"term": "Merkle directed acyclic graph",
|
||||
"definition": "Merkle directed acyclic graph"
|
||||
},
|
||||
{
|
||||
"term": "Message padding",
|
||||
"definition": "Message padding"
|
||||
},
|
||||
{
|
||||
"term": "Onion routing",
|
||||
"definition": "Onion routing"
|
||||
},
|
||||
{
|
||||
"term": "Overlay network",
|
||||
"definition": "Overlay network"
|
||||
},
|
||||
{
|
||||
"term": "Pairwise pseudonymous identifier",
|
||||
"definition": "Pairwise pseudonymous identifier"
|
||||
},
|
||||
{
|
||||
"term": "Peer-to-peer",
|
||||
"definition": "Peer-to-peer"
|
||||
},
|
||||
{
|
||||
"term": "Perfect forward secrecy",
|
||||
"definition": "Forward secrecy"
|
||||
},
|
||||
{
|
||||
"term": "Post-compromise security",
|
||||
"definition": "Post-compromise security"
|
||||
},
|
||||
{
|
||||
"term": "Post-quantum cryptography",
|
||||
"definition": "Post-quantum cryptography"
|
||||
},
|
||||
{
|
||||
"term": "Proxied peer-to-peer",
|
||||
"definition": "Proxied peer-to-peer"
|
||||
},
|
||||
{
|
||||
"term": "Recovery from compromise",
|
||||
"definition": "Post-compromise security"
|
||||
},
|
||||
{
|
||||
"term": "User identity",
|
||||
"definition": "User identity"
|
||||
}
|
||||
]
|
||||
@@ -22,7 +22,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<svg class="fill-grey-black dark:fill-white fixed right-5 top-5 cursor-pointer close-overlay-btn" id="cross" width="16" height="16" viewBox="0 0 13 13" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="close-overlay-btn" id="cross" width="16" height="16" viewBox="0 0 13 13" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.7973 11.5525L7.59762 6.49833L12.7947 1.44675C13.055 1.19371 13.0658 0.771991 12.8188 0.505331C12.5718 0.238674 12.1602 0.227644 11.8999 0.480681L6.65343 5.58028L1.09979 0.182228C0.839522 -0.070157 0.427909 -0.059127 0.18094 0.207531C-0.0660305 0.474191 -0.0552645 0.895911 0.205003 1.14894L5.70862 6.49833L0.20247 11.851C-0.0577975 12.104 -0.0685635 12.5257 0.178407 12.7924C0.306324 12.9306 0.477936 13 0.650181 13C0.811033 13 0.971873 12.9397 1.09726 12.817L6.65343 7.41639L11.9025 12.5186C12.0285 12.6406 12.1893 12.7015 12.3495 12.7015C12.5218 12.7015 12.6934 12.6321 12.8213 12.4939C13.0689 12.2273 13.0582 11.8062 12.7973 11.5525Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<section class="hidden xl:block h-screen pt-[66px] bg-white dark:bg-gradient-radial-mobile dark:lg:bg-gradient-radial">
|
||||
<div class="container m-auto h-full flex items-center justify-between px-5">
|
||||
<div class="flex flex-col items-start justify-center w-full h-full">
|
||||
<p class="text-[38px] leading-[43px] font-bold max-w-[500px] mb-[30px] primary-header-contact">{{ header | i18n({}, lang ) | safe }}</p>
|
||||
<p class="text-[20px] leading-[28px] text-[#606C71] dark:text-white font-bold max-w-[475px] mb-[80px] secondary-header-contact">{{ "contact-hero-subheader" | i18n({}, lang ) | safe }}</p>
|
||||
<h1 class="text-[38px] leading-[43px] font-bold max-w-[500px] mb-[30px] primary-header-contact">{{ header | i18n({}, lang ) | safe }}</h1>
|
||||
<h2 class="text-[20px] leading-[28px] text-[#606C71] dark:text-white font-bold max-w-[475px] mb-[80px] secondary-header-contact">{{ "contact-hero-subheader" | i18n({}, lang ) | safe }}</h2>
|
||||
<p class="text-grey-black dark:text-white text-base mb-[16px]">
|
||||
{{ "contact-hero-p-1" | i18n({}, lang ) | safe }}
|
||||
</p>
|
||||
@@ -43,7 +43,7 @@
|
||||
<section class="block xl:hidden pt-[106px] pb-[90px] bg-white dark:bg-gradient-radial-mobile dark:lg:bg-gradient-radial">
|
||||
<div class="container m-auto h-full px-5">
|
||||
<div class="flex flex-col items-center">
|
||||
<p class="text-[28px] font-bold text-center max-w-[602px] mb-[40px] primary-header-contact">{{ header | i18n({}, lang ) | safe }}</p>
|
||||
<h1 class="text-[28px] font-bold text-center max-w-[602px] mb-[40px] primary-header-contact">{{ header | i18n({}, lang ) | safe }}</h1>
|
||||
<p class="text-[20px] leading-[28px] text-grey-black dark:text-white font-medium mb-[30px]">{{ "to-make-a-connection" | i18n({}, lang ) | safe }}</p>
|
||||
|
||||
<div class="flex flex-col justify-center items-center p-4 w-full max-w-[468px] min-h-[131px] rounded-[30px] border-[1px] border-[#A8B0B4] dark:border-white border-opacity-60 mb-6 relative">
|
||||
@@ -88,7 +88,7 @@
|
||||
<div class="hidden md:block xl:hidden for-tablet">
|
||||
<div class="contact-tab">
|
||||
<div class="flex items-center justify-between my-[40px] contact-tab-btn cursor-pointer">
|
||||
<p class="text-xl font-bold">{{ "scan-the-qr-code-with-the-simplex-chat-app" | i18n({}, lang ) | safe }}</p>
|
||||
<h2 class="text-xl font-bold">{{ "scan-the-qr-code-with-the-simplex-chat-app" | i18n({}, lang ) | safe }}</h2>
|
||||
<svg class="fill-grey-black dark:fill-white" width="10" height="5" viewBox="0 0 10 5" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.40813 4.79332C8.69689 5.06889 9.16507 5.06889 9.45384 4.79332C9.7426 4.51775 9.7426 4.07097 9.45384 3.7954L5.69327 0.206676C5.65717 0.17223 5.61827 0.142089 5.57727 0.116255C5.29026 -0.064587 4.90023 -0.0344467 4.64756 0.206676L0.886983 3.7954C0.598219 4.07097 0.598219 4.51775 0.886983 4.79332C1.17575 5.06889 1.64393 5.06889 1.93269 4.79332L5.17041 1.70356L8.40813 4.79332Z"/>
|
||||
</svg>
|
||||
@@ -108,7 +108,7 @@
|
||||
<div class="hidden xl:block">
|
||||
<div class="contact-tab">
|
||||
<div class="flex items-center justify-between my-[40px] contact-tab-btn cursor-pointer">
|
||||
<p class="text-xl font-bold">{{ "installing-simplex-chat-to-terminal" | i18n({}, lang ) | safe }}</p>
|
||||
<h2 class="text-xl font-bold">{{ "installing-simplex-chat-to-terminal" | i18n({}, lang ) | safe }}</h2>
|
||||
<svg class="fill-grey-black dark:fill-white" width="10" height="5" viewBox="0 0 10 5" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.40813 4.79332C8.69689 5.06889 9.16507 5.06889 9.45384 4.79332C9.7426 4.51775 9.7426 4.07097 9.45384 3.7954L5.69327 0.206676C5.65717 0.17223 5.61827 0.142089 5.57727 0.116255C5.29026 -0.064587 4.90023 -0.0344467 4.64756 0.206676L0.886983 3.7954C0.598219 4.07097 0.598219 4.51775 0.886983 4.79332C1.17575 5.06889 1.64393 5.06889 1.93269 4.79332L5.17041 1.70356L8.40813 4.79332Z"/>
|
||||
</svg>
|
||||
@@ -139,7 +139,7 @@
|
||||
|
||||
<div class="contact-tab">
|
||||
<div class="flex items-center justify-between my-[40px] contact-tab-btn cursor-pointer">
|
||||
<p class="text-xl font-bold">{{ "if-you-already-installed-simplex-chat-for-the-terminal" | i18n({}, lang ) | safe }}</p>
|
||||
<h2 class="text-xl font-bold">{{ "if-you-already-installed-simplex-chat-for-the-terminal" | i18n({}, lang ) | safe }}</h2>
|
||||
<svg class="fill-grey-black dark:fill-white" width="10" height="5" viewBox="0 0 10 5" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.40813 4.79332C8.69689 5.06889 9.16507 5.06889 9.45384 4.79332C9.7426 4.51775 9.7426 4.07097 9.45384 3.7954L5.69327 0.206676C5.65717 0.17223 5.61827 0.142089 5.57727 0.116255C5.29026 -0.064587 4.90023 -0.0344467 4.64756 0.206676L0.886983 3.7954C0.598219 4.07097 0.598219 4.51775 0.886983 4.79332C1.17575 5.06889 1.64393 5.06889 1.93269 4.79332L5.17041 1.70356L8.40813 4.79332Z"/>
|
||||
</svg>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<div class="h-[40px] flex gap-4 justify-center">
|
||||
<a href="https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion">
|
||||
<svg class="fill-primary-light dark:fill-primary-dark" width="40" height="40" viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.585 0C11.9025 0 0 11.9025 0 26.585C0 41.2674 11.9025 53.1699 26.585 53.1699C41.2674 53.1699 53.1699 41.2674 53.1699 26.585C53.1699 11.9025 41.2674 0 26.585 0ZM11.3862 17.3518L17.6366 23.4373L23.9313 17.3088L17.6787 11.2209L20.866 8.1179L27.1187 14.2061L33.4932 8L36.6199 11.044L30.2448 17.25L36.4982 23.3379L42.8733 17.1321L46 20.1761L39.6249 26.3818L45.8789 32.4702L42.6916 35.5732L36.4376 29.4848L30.0631 35.6906L36.3171 41.7791L33.1299 44.8821L26.8759 38.7936L20.5026 45L17.3759 41.956L23.8003 35.693L17.5493 29.6073L11.1255 35.8621L8 32.8194L14.4244 26.5646L8.17397 20.4792L11.3862 17.3518ZM27.0125 32.5656L33.311 26.4408L27.0576 20.3535L27.0568 20.3516L20.7615 26.4799L27.0125 32.5656Z" fill="#0053D0"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.585 0C11.9025 0 0 11.9025 0 26.585C0 41.2674 11.9025 53.1699 26.585 53.1699C41.2674 53.1699 53.1699 41.2674 53.1699 26.585C53.1699 11.9025 41.2674 0 26.585 0ZM11.3862 17.3518L17.6366 23.4373L23.9313 17.3088L17.6787 11.2209L20.866 8.1179L27.1187 14.2061L33.4932 8L36.6199 11.044L30.2448 17.25L36.4982 23.3379L42.8733 17.1321L46 20.1761L39.6249 26.3818L45.8789 32.4702L42.6916 35.5732L36.4376 29.4848L30.0631 35.6906L36.3171 41.7791L33.1299 44.8821L26.8759 38.7936L20.5026 45L17.3759 41.956L23.8003 35.693L17.5493 29.6073L11.1255 35.8621L8 32.8194L14.4244 26.5646L8.17397 20.4792L11.3862 17.3518ZM27.0125 32.5656L33.311 26.4408L27.0576 20.3535L27.0568 20.3516L20.7615 26.4799L27.0125 32.5656Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://github.com/simplex-chat" target="_blank">
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<img class="static-phone-mobile md:hidden" src="/img/new/mobile-hero.png" alt="" />
|
||||
|
||||
<article class="w-full xl:max-w-[600px] landing-page-header-article">
|
||||
<p class="primary-header text-center xl:text-left xl:rtl:text-right font-bold text-[38px] md:text-[55px] leading-[46px] md:leading-[63px] mb-2 xl:mb-8">{{ "hero-header" | i18n({}, lang ) | safe }}</p>
|
||||
<p class="secondary-header text-center xl:text-left xl:rtl:text-right font-bold text-[28px] md:text-[38px] leading-[36px] md:leading-[43px] mb-2 xl:mb-8 tracking-[0.01em]">{{ "hero-subheader" | i18n({}, lang ) | safe }}</p>
|
||||
<h1 class="primary-header text-center xl:text-left xl:rtl:text-right font-bold text-[38px] md:text-[55px] leading-[46px] md:leading-[63px] mb-2 xl:mb-8">{{ "hero-header" | i18n({}, lang ) | safe }}</h1>
|
||||
<h2 class="secondary-header text-center xl:text-left xl:rtl:text-right font-bold text-[28px] md:text-[38px] leading-[36px] md:leading-[43px] mb-2 xl:mb-8 tracking-[0.01em]">{{ "hero-subheader" | i18n({}, lang ) | safe }}</h2>
|
||||
<p class="landing-page-header-article-paragraph text-black dark:text-white text-center xl:text-justify text-[16px] leading-[24px] mb-[20px] header-description">
|
||||
{{ "hero-p-1" | i18n({}, lang ) | safe }}
|
||||
</p>
|
||||
@@ -87,9 +87,9 @@
|
||||
</div>
|
||||
|
||||
<article class="w-full xl:max-w-[600px] landing-page-header-article px-5">
|
||||
<p class="text-active-blue text-center xl:text-left xl:rtl:text-right font-bold text-[28px] md:text-[35px] leading-[36px] md:leading-[43px] mb-[28px]">
|
||||
<h2 class="text-active-blue text-center xl:text-left xl:rtl:text-right font-bold text-[28px] md:text-[35px] leading-[36px] md:leading-[43px] mb-[28px]">
|
||||
{{ "hero-2-header" | i18n({}, lang ) | safe }}
|
||||
</p>
|
||||
</h2>
|
||||
<p class="text-center text-black dark:text-white xl:text-justify leading-[24px] text-[16px] mb-10 xl:mb-[25px] header-description">
|
||||
{{ "hero-2-header-desc" | i18n({}, lang ) | safe }}
|
||||
</p>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
{% include "navbar.html" %}
|
||||
</section>
|
||||
|
||||
{{ content | safe }}
|
||||
{{ content | applyGlossary | safe }}
|
||||
|
||||
{% include "footer.html" %}
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@
|
||||
</a></li>
|
||||
<li><a href="/docs/protocol/simplex-chat.html" class="lg:px-[20px] inline-block"
|
||||
>{{ "chat-protocol" | i18n({}, lang ) | safe }}</a></li>
|
||||
<li><a href="/docs/glossary.html" class="lg:px-[20px] inline-block"
|
||||
>{{ "glossary" | i18n({}, lang ) | safe }}</a></li>
|
||||
|
||||
<hr class=" h-[1px] w-full dark:opacity-[0.1]">
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{# join simplex #}
|
||||
<section id="join-simplex" class="bg-primary-bg-light dark:bg-primary-bg-dark lg:h-[855px] py-[90px] px-5">
|
||||
<div class="container flex flex-col items-center">
|
||||
<p class="text-[38px] leading-[36px] md:leading-[55px] text-grey-black dark:text-white text-center font-bold mb-5"><span class="text-active-blue">{{ "join" | i18n({}, lang ) | safe }}</span> SimpleX</p>
|
||||
<h2 class="text-[38px] leading-[36px] md:leading-[55px] text-grey-black dark:text-white text-center font-bold mb-5"><span class="text-active-blue">{{ "join" | i18n({}, lang ) | safe }}</span> SimpleX</h2>
|
||||
<p class="text-black dark:text-white text-base text-center mb-14">{{ "we-invite-you-to-join-the-conversation" | i18n({}, lang ) | safe }}</p>
|
||||
|
||||
<div class="flex flex-col items-center gap-5 self-stretch mb-12">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section id="why-simplex" class="bg-primary-bg-light dark:bg-primary-bg-dark py-[90px] overflow-hidden px-0 sm:px-1 xl:h-[888px]">
|
||||
<div class="container scale-100">
|
||||
<p class="text-grey-black dark:text-white text-[35px] leading-[45px] md:leading-[55px] lg:text-[45px] text-center font-bold mb-14 px-5 sm:px-4">{{ "why-simplex-is" | i18n({}, lang ) | safe }} <span class="gradient-text">{{ "unique" | i18n({}, lang ) | safe }}</span></p>
|
||||
<h2 class="text-grey-black dark:text-white text-[35px] leading-[45px] md:leading-[55px] lg:text-[45px] text-center font-bold mb-14 px-5 sm:px-4">{{ "why-simplex-is" | i18n({}, lang ) | safe }} <span class="gradient-text">{{ "unique" | i18n({}, lang ) | safe }}</span></h2>
|
||||
|
||||
<div class="swiper unique-swiper px-5 sm:px-4 py-2">
|
||||
<div class="swiper-wrapper mb-16">
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
<div class="card-content absolute md:static px-4 md:px-0 bottom-[80px] right-1 left-1 h-[180px] md:h-fit pt-5 lg:pt-0 bg-primary-bg-light dark:bg-primary-bg-dark">
|
||||
<div class="content-head">
|
||||
<p class="text-[35px] lg:text-[65px] font-bold tracking-[0.06em] text-active-blue text-center md:text-left md:rtl:text-right">#{{ section.id }}</p>
|
||||
<p class="w-full max-w-[617px] text-[25px] leading-[33px] lg:text-[35px] lg:leading-[45px] text-center md:text-left md:rtl:text-right font-bold text-grey-black dark:text-white">{{ section.title | i18n({}, lang ) | safe }}</p>
|
||||
<h1 class="text-[35px] lg:text-[65px] font-bold tracking-[0.06em] text-active-blue text-center md:text-left md:rtl:text-right">#{{ section.id }}</h1>
|
||||
<h3 class="w-full max-w-[617px] text-[25px] leading-[33px] lg:text-[35px] lg:leading-[45px] text-center md:text-left md:rtl:text-right font-bold text-grey-black dark:text-white">{{ section.title | i18n({}, lang ) | safe }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="content-body py-5 md:py-7">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<section id="simplex-explained" class="bg-primary-bg-light dark:bg-primary-bg-dark lg:h-[890px] py-[90px] px-5">
|
||||
<div class="container">
|
||||
<p class="text-[35px] leading-[45px] md:leading-[55px] lg:text-[38px] text-center font-bold text-grey-black dark:text-white mb-9">{{ "simplex-explained" | i18n({}, lang ) | safe }}</p>
|
||||
<h2 class="text-[35px] leading-[45px] md:leading-[55px] lg:text-[38px] text-center font-bold text-grey-black dark:text-white mb-9">{{ "simplex-explained" | i18n({}, lang ) | safe }}</h2>
|
||||
|
||||
<!-- Tab links -->
|
||||
<div class="tabs hidden md:flex gap-2 mb-24">
|
||||
|
||||
@@ -35,7 +35,7 @@ active_blog: true
|
||||
|
||||
<section class="py-10 px-5 mt-[66px]" id="blog-list">
|
||||
<div class="container">
|
||||
<p class="text-[38px] text-center font-bold text-active-blue mb-9">Latest news</p>
|
||||
<h1 class="text-[38px] text-center font-bold text-active-blue mb-9">Latest news</h1>
|
||||
|
||||
{% for blog in collections.blogs %}
|
||||
{% if not(blog.data.draft) %}
|
||||
|
||||
+232
-6
@@ -428,13 +428,13 @@ header nav {
|
||||
transform: translateY(40px);
|
||||
}
|
||||
|
||||
.card:not(.no-hover):hover > div:nth-child(1) {
|
||||
.card:not(.no-hover):hover > div:nth-child(1),
|
||||
.card.hovered > div:nth-child(1) {
|
||||
height: 200px;
|
||||
padding: 12px 10px;
|
||||
}
|
||||
.card:not(.no-hover):hover > div:nth-child(2) {
|
||||
.card:not(.no-hover):hover > div:nth-child(2),
|
||||
.card.hovered > div:nth-child(2) {
|
||||
height: 270px;
|
||||
padding: 8px 24px;
|
||||
}
|
||||
.card.card-active > div:nth-child(2) {
|
||||
height: 480px;
|
||||
@@ -443,13 +443,16 @@ header nav {
|
||||
.card:not(.no-hover):hover > div:nth-child(2) > *:nth-child(2),
|
||||
.card:not(.no-hover):hover > div:nth-child(2) > *:nth-child(3),
|
||||
.card.card-active > div:nth-child(2) > *:nth-child(2),
|
||||
.card.card-active > div:nth-child(2) > *:nth-child(3) {
|
||||
.card.card-active > div:nth-child(2) > *:nth-child(3),
|
||||
.card:not(.no-hover).hovered > div:nth-child(2) > *:nth-child(2),
|
||||
.card:not(.no-hover).hovered > div:nth-child(2) > *:nth-child(3) {
|
||||
opacity: 1;
|
||||
max-height: 480px;
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
.card:not(.no-hover):hover > div:nth-child(2) > *:nth-child(3){
|
||||
.card:not(.no-hover):hover > div:nth-child(2) > *:nth-child(3),
|
||||
.card:not(.no-hover).hovered > div:nth-child(2) > *:nth-child(3) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@@ -710,4 +713,227 @@ p a{
|
||||
.contact-tab.active svg,
|
||||
.contact-tab:hover svg{
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
/* Glossary */
|
||||
.glossary-term{
|
||||
display: inline-block;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dashed;
|
||||
text-underline-offset: 3px;
|
||||
position: relative;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.glossary-term::before{
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.glossary-tooltip {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
width: 320px;
|
||||
max-width: 100%;
|
||||
z-index: 10001;
|
||||
transition: opacity .5s;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.glossary-tooltip .tooltip-content{
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
text-align: left;
|
||||
padding: .8rem 1.2rem;
|
||||
border-radius: 7px;
|
||||
font-size: 14px;
|
||||
line-height: 1.3rem;
|
||||
box-shadow: 0 5px 10px rgba(0,0,0,.08);
|
||||
}
|
||||
|
||||
.dark .glossary-tooltip .tooltip-content{
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tooltip-title{
|
||||
margin-bottom: 0.5rem;
|
||||
color: #0197FF;
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.dark .tooltip-title{
|
||||
color: #70F0F9;
|
||||
}
|
||||
|
||||
.glossary-tooltip .read-more-btn{
|
||||
color: #0053D0;
|
||||
display: block;
|
||||
text-decoration: underline;
|
||||
margin-top: .8rem;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.dark .glossary-tooltip .read-more-btn{
|
||||
color: #70F0F9;
|
||||
}
|
||||
|
||||
.glossary-overlay{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: transparent;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.75rem;
|
||||
z-index: 10005;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.glossary-overlay{
|
||||
padding: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.glossary-overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color:#F3F6F7;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.dark .glossary-overlay::before {
|
||||
content: '';
|
||||
background-color: #0C0B13;
|
||||
}
|
||||
|
||||
.glossary-overlay .overlay-card{
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
opacity: 1;
|
||||
height: 100%;
|
||||
z-index: 10006;
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0px 3px 12px rgba(0, 0, 0, 0.2);
|
||||
padding: 2.5rem 1.5rem;
|
||||
overflow: auto;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
.dark .glossary-overlay .overlay-card{
|
||||
background-color: #17203D;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.glossary-overlay .overlay-card{
|
||||
padding: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.glossary-overlay .overlay-card{
|
||||
width: fit-content;
|
||||
max-width: 558px;
|
||||
height: fit-content;
|
||||
max-height: 660px;
|
||||
}
|
||||
}
|
||||
|
||||
.glossary-overlay .overlay-card .overlay-title{
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.25rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(1 151 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.glossary-overlay .overlay-card .overlay-content{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
color: #3F484B;
|
||||
}
|
||||
|
||||
.dark .glossary-overlay .overlay-card .overlay-content{
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.close-overlay-btn{
|
||||
fill: #3F484B;
|
||||
position: fixed;
|
||||
right: 1.25rem;
|
||||
top: 1.25rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dark .close-overlay-btn{
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.glossary-overlay ul {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.glossary-overlay ul,
|
||||
.glossary-overlay ol {
|
||||
list-style-position: inside;
|
||||
overflow: auto;
|
||||
margin: 1rem 0;
|
||||
/* padding-left: 1rem; */
|
||||
}
|
||||
|
||||
.glossary-overlay ul li,
|
||||
.glossary-overlay ol li {
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
-webkit-margin-start: 1.1rem;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.dark .glossary-overlay ul li,
|
||||
.dark .glossary-overlay ol li {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.glossary-overlay ul li::marker,
|
||||
.glossary-overlay ol li::marker {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.dark .glossary-overlay ul li::marker,
|
||||
.dark .glossary-overlay ol li::marker {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.glossary-overlay ul li a,
|
||||
.glossary-overlay ol li a {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.glossary-overlay ul li {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.glossary-overlay ol li {
|
||||
list-style: decimal;
|
||||
}
|
||||
+10
-10
@@ -11,7 +11,7 @@ active_home: true
|
||||
|
||||
<section id="why-privacy" class="bg-secondary-bg-light dark:bg-secondary-bg-dark py-[90px] px-5 lg:h-[888px]">
|
||||
<div class="container">
|
||||
<p class="text-grey-black dark:text-white text-[35px] leading-[45px] md:leading-[55px] lg:text-[45px] text-center font-bold mb-4 md:mb-8">{{ "privacy-matters-section-header" | i18n({}, lang ) | safe }}</p>
|
||||
<h2 class="text-grey-black dark:text-white text-[35px] leading-[45px] md:leading-[55px] lg:text-[45px] text-center font-bold mb-4 md:mb-8">{{ "privacy-matters-section-header" | i18n({}, lang ) | safe }}</h2>
|
||||
<p class="text-center text-[18px] md:text-[20px] font-medium mb-7 md:mb-16 lg:mb-20 text-black dark:text-white">{{ "privacy-matters-section-subheader" | i18n({}, lang ) | safe }}</p>
|
||||
<div class="flex flex-col lg:flex-row gap-[20px] mb-[62px] lg:mb-[90px]">
|
||||
|
||||
@@ -41,7 +41,7 @@ active_home: true
|
||||
{# Features #}
|
||||
<section id="features" class="bg-secondary-bg-light dark:bg-secondary-bg-dark py-[95px] px-5 lg:h-[888px]">
|
||||
<div class="container">
|
||||
<p class="text-[35px] leading-[45px] md:leading-[55px] lg:text-[45px] text-center font-bold gradient-text mb-20">{{ "features" | i18n({}, lang ) | safe }}</p>
|
||||
<h2 class="text-[35px] leading-[45px] md:leading-[55px] lg:text-[45px] text-center font-bold gradient-text mb-20">{{ "features" | i18n({}, lang ) | safe }}</h2>
|
||||
|
||||
<div class="mb-[50px] grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-x-10 gap-y-32">
|
||||
{% for feature in features.sections %}
|
||||
@@ -60,7 +60,7 @@ active_home: true
|
||||
{# what makes simplex private #}
|
||||
<section id="privacy" class="bg-primary-bg-light dark:bg-primary-bg-dark py-[90px] overflow-hidden px-5 lg:h-[888px]">
|
||||
<div class="container scale-100">
|
||||
<p class="text-grey-black dark:text-white text-[35px] leading-[45px] md:leading-[55px] lg:text-[45px] text-center font-bold mb-20">{{ "simplex-private-section-header" | i18n({}, lang ) | safe }}</p>
|
||||
<h2 class="text-grey-black dark:text-white text-[35px] leading-[45px] md:leading-[55px] lg:text-[45px] text-center font-bold mb-20">{{ "simplex-private-section-header" | i18n({}, lang ) | safe }}</h2>
|
||||
|
||||
<div class="swiper private-swiper overflow-hidden px-4 py-2">
|
||||
<div class="swiper-wrapper mb-16">
|
||||
@@ -71,8 +71,8 @@ active_home: true
|
||||
<img class="w-full max-w-[223px] h-full max-h-[226px] dark:hidden" src="{{ section.imgLight }}" alt=""/>
|
||||
<img class="w-full max-w-[223px] h-full max-h-[226px] hidden dark:block" src="{{ section.imgDark }}" alt=""/>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-between h-[138px] absolute bottom-0 py-6 px-6 bg-card-desc-bg-light dark:bg-card-desc-bg-dark rounded-b-[20px]">
|
||||
<p class="text-grey-black dark:text-white text-[18px] my-4 font-bold leading-[26px] tracking-[0.01em] text-center">{{ section.title | i18n({}, lang ) | safe }}</p>
|
||||
<div class="card-content flex flex-col items-center justify-between h-[138px] absolute bottom-0 py-6 px-6 bg-card-desc-bg-light dark:bg-card-desc-bg-dark rounded-b-[20px]">
|
||||
<h3 class="text-grey-black dark:text-white text-[18px] my-4 font-bold leading-[26px] tracking-[0.01em] text-center">{{ section.title | i18n({}, lang ) | safe }}</h3>
|
||||
<div class="flex-1 py-3 flex flex-col gap-3">
|
||||
{% for point in section.points %}
|
||||
<p class="text-grey-black dark:text-white text-[14px] text-center">{{ point | i18n({}, lang ) | safe }}</p>
|
||||
@@ -102,7 +102,7 @@ active_home: true
|
||||
{# Network #}
|
||||
<section id="network" class="bg-secondary-bg-light dark:bg-secondary-bg-dark lg:h-[642px] py-[95px] px-5">
|
||||
<div class="container">
|
||||
<p class="text-grey-black dark:text-white text-[35px] leading-[45px] md:leading-[55px] lg:text-[45px] text-center font-bold mb-5">{{ "simplex-network-section-header" | i18n({}, lang ) | safe }}</p>
|
||||
<h2 class="text-grey-black dark:text-white text-[35px] leading-[45px] md:leading-[55px] lg:text-[45px] text-center font-bold mb-5">{{ "simplex-network-section-header" | i18n({}, lang ) | safe }}</h2>
|
||||
<p class="text-black dark:text-white text-[16px] font-normal text-center mb-16">{{ "simplex-network-section-desc" | i18n({}, lang ) | safe }}</p>
|
||||
|
||||
<div class="flex flex-col lg:flex-row justify-between gap-12 md:gap-14 lg:gap-16">
|
||||
@@ -112,7 +112,7 @@ active_home: true
|
||||
<img src="/img/new/network-1-dark.svg" alt="" class="hidden dark:block"/>
|
||||
</div>
|
||||
<div class="md:flex-[2] flex flex-col items-center justify-center">
|
||||
<p class="text-active-blue text-xl font-bold text-center md:text-left lg:text-center self-stretch">{{ "simplex-network-1-header" | i18n({}, lang ) | safe }}</p>
|
||||
<h3 class="text-active-blue text-xl font-bold text-center md:text-left lg:text-center self-stretch">{{ "simplex-network-1-header" | i18n({}, lang ) | safe }}</h3>
|
||||
<p class="text-black dark:text-white text-base font-normal text-center md:text-left lg:text-center">
|
||||
{{ "simplex-network-1-desc" | i18n({}, lang ) | safe }} <a href="javascript:void(0)" data-show-overlay="{{ simplex_network_overlay.sections[0].overlayContent.overlayId }}" class="open-overlay-btn">{{ "simplex-network-1-overlay-linktext" | i18n({}, lang ) | safe }}</a>.
|
||||
{{ overlay(simplex_network_overlay.sections[0],lang) }}
|
||||
@@ -128,7 +128,7 @@ active_home: true
|
||||
<img src="/img/new/network-2-dark.svg" alt="" class="hidden dark:block"/>
|
||||
</div>
|
||||
<div class="md:flex-[2] flex flex-col items-center justify-center">
|
||||
<p class="text-active-blue text-xl font-bold text-center md:text-left lg:text-center self-stretch">{{ "simplex-network-2-header" | i18n({}, lang ) | safe }}</p>
|
||||
<h3 class="text-active-blue text-xl font-bold text-center md:text-left lg:text-center self-stretch">{{ "simplex-network-2-header" | i18n({}, lang ) | safe }}</h3>
|
||||
<p class="text-black dark:text-white text-base font-normal text-center md:text-left lg:text-center">
|
||||
{{ "simplex-network-2-desc" | i18n({}, lang ) | safe }}
|
||||
</p>
|
||||
@@ -143,7 +143,7 @@ active_home: true
|
||||
<img src="/img/new/network-3-dark.svg" alt="" class="hidden dark:block"/>
|
||||
</div>
|
||||
<div class="md:flex-[2] flex flex-col items-center justify-center">
|
||||
<p class="text-active-blue text-xl font-bold text-center md:text-left lg:text-center self-stretch">{{ "simplex-network-3-header" | i18n({}, lang ) | safe }}</p>
|
||||
<h3 class="text-active-blue text-xl font-bold text-center md:text-left lg:text-center self-stretch">{{ "simplex-network-3-header" | i18n({}, lang ) | safe }}</h3>
|
||||
<p class="text-black dark:text-white text-base font-normal text-center md:text-left lg:text-center">
|
||||
{{ "simplex-network-3-desc" | i18n({}, lang ) | safe }}
|
||||
</p>
|
||||
@@ -163,7 +163,7 @@ active_home: true
|
||||
{# Comparison #}
|
||||
<section id="comparison" class="bg-secondary-bg-light dark:bg-secondary-bg-dark lg:h-[950px] py-[90px] px-5">
|
||||
<div class="text-grey-black dark:text-white container flex flex-col">
|
||||
<p class="text-[35px] leading-[43px] md:leading-[55px] lg:leading-[36px] text-center font-bold mb-12 lg:mb-[90px]">{{ "comparison-section-header" | i18n({}, lang ) | safe }}</p>
|
||||
<h2 class="text-[35px] leading-[43px] md:leading-[55px] lg:leading-[36px] text-center font-bold mb-12 lg:mb-[90px]">{{ "comparison-section-header" | i18n({}, lang ) | safe }}</h2>
|
||||
|
||||
<div class="w-full overflow-auto">
|
||||
<table class="w-full border-separate border-spacing-x-5 border-spacing-y-2 mb-14">
|
||||
|
||||
+139
-2
@@ -65,6 +65,19 @@ const privateSwiper = new Swiper('.private-swiper', {
|
||||
allowTouchMove: true,
|
||||
}
|
||||
},
|
||||
on: {
|
||||
slideChange: function () {
|
||||
const privateSwiperGlossaryTerms = document.querySelectorAll('.private-swiper .glossary-term');
|
||||
privateSwiperGlossaryTerms.forEach(function (glossaryTerm) {
|
||||
var tooltipId = glossaryTerm.getAttribute('data-glossary');
|
||||
var tooltip = document.getElementById(tooltipId);
|
||||
tooltip.style.visibility = 'hidden';
|
||||
tooltip.style.opacity = '0';
|
||||
const privateSwiper = glossaryTerm.closest('.private-swiper')
|
||||
if (privateSwiper) glossaryTerm.closest('.card').classList.remove('hovered');
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const simplexExplainedSwiper = new Swiper(".simplex-explained-swiper", {
|
||||
@@ -86,6 +99,17 @@ const simplexExplainedSwiper = new Swiper(".simplex-explained-swiper", {
|
||||
pagination: {
|
||||
el: ".simplex-explained-swiper-pagination",
|
||||
clickable: true
|
||||
},
|
||||
on: {
|
||||
slideChange: function () {
|
||||
const explainedSwiperGlossaryTerms = document.querySelectorAll('.simplex-explained-swiper .glossary-term');
|
||||
explainedSwiperGlossaryTerms.forEach(function (glossaryTerm) {
|
||||
var tooltipId = glossaryTerm.getAttribute('data-glossary');
|
||||
var tooltip = document.getElementById(tooltipId);
|
||||
tooltip.style.visibility = 'hidden';
|
||||
tooltip.style.opacity = '0';
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -163,6 +187,13 @@ function openOverlay() {
|
||||
const scrollToEl = document.getElementById(scrollTo)
|
||||
if (scrollToEl) scrollToEl.scrollIntoView(true)
|
||||
}
|
||||
|
||||
const currentOpenedGlossaryOverlay = document.querySelector('.glossary-overlay.flex')
|
||||
if (currentOpenedGlossaryOverlay) {
|
||||
currentOpenedGlossaryOverlay.classList.remove('flex')
|
||||
currentOpenedGlossaryOverlay.classList.add('hidden')
|
||||
}
|
||||
|
||||
el.classList.remove('hidden')
|
||||
el.classList.add('flex')
|
||||
document.body.classList.add('lock-scroll')
|
||||
@@ -170,5 +201,111 @@ function openOverlay() {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', openOverlay);
|
||||
window.addEventListener('hashchange', openOverlay);
|
||||
function updatePointerEventsInPrivateSwiperCards() {
|
||||
var privateSwiperCards = document.querySelectorAll('.private-swiper .card')
|
||||
|
||||
privateSwiperCards.forEach(function (card) {
|
||||
var cardContent = card.querySelector('.card-content')
|
||||
|
||||
function updatePointerEvents() {
|
||||
var cardContentGlossaryTerms = cardContent.querySelectorAll('.glossary-term')
|
||||
cardContentGlossaryTerms.forEach(function (glossaryTerm) {
|
||||
if (cardContent.offsetHeight >= 270) {
|
||||
glossaryTerm.style.pointerEvents = 'all'
|
||||
} else {
|
||||
glossaryTerm.style.pointerEvents = 'none'
|
||||
}
|
||||
})
|
||||
}
|
||||
updatePointerEvents()
|
||||
|
||||
cardContent.addEventListener('click', updatePointerEvents)
|
||||
cardContent.addEventListener('mousemove', updatePointerEvents)
|
||||
})
|
||||
}
|
||||
|
||||
function updateTooltipPosition(glossaryTerm, tooltip) {
|
||||
var glossaryTermOffset = glossaryTerm.getBoundingClientRect()
|
||||
var tooltipOffset = tooltip.getBoundingClientRect()
|
||||
|
||||
if (glossaryTermOffset.top >= tooltipOffset.height) {
|
||||
tooltip.style.top = glossaryTermOffset.top - tooltipOffset.height + 'px'
|
||||
} else {
|
||||
tooltip.style.top = glossaryTermOffset.bottom + 'px'
|
||||
}
|
||||
|
||||
var leftPosition = glossaryTermOffset.left + glossaryTerm.offsetWidth / 2 - tooltip.offsetWidth / 2
|
||||
if (leftPosition < 0) {
|
||||
tooltip.style.left = '0px'
|
||||
} else if (leftPosition + tooltip.offsetWidth > window.innerWidth) {
|
||||
tooltip.style.left = window.innerWidth - tooltip.offsetWidth + 'px'
|
||||
} else {
|
||||
tooltip.style.left = leftPosition + 'px'
|
||||
}
|
||||
}
|
||||
|
||||
function setupTooltip(glossaryTerm) {
|
||||
var tooltipId = glossaryTerm.getAttribute('data-glossary')
|
||||
var tooltip = document.getElementById(tooltipId)
|
||||
|
||||
function showTooltip() {
|
||||
tooltip.style.visibility = 'visible'
|
||||
tooltip.style.opacity = '1'
|
||||
updateTooltipPosition(glossaryTerm, tooltip)
|
||||
const privateSwiper = glossaryTerm.closest('.private-swiper')
|
||||
if (privateSwiper) glossaryTerm.closest('.card').classList.add('hovered')
|
||||
}
|
||||
|
||||
function hideTooltip() {
|
||||
tooltip.style.visibility = 'hidden'
|
||||
tooltip.style.opacity = '0'
|
||||
const privateSwiper = glossaryTerm.closest('.private-swiper')
|
||||
if (privateSwiper) glossaryTerm.closest('.card').classList.remove('hovered')
|
||||
if (!glossaryTerm.matches(':hover') && !tooltip.matches(':hover')) {
|
||||
glossaryTerm.classList.remove('active-term')
|
||||
tooltip.removeEventListener('mouseover', showTooltip)
|
||||
tooltip.removeEventListener('mouseout', hideTooltip)
|
||||
}
|
||||
}
|
||||
|
||||
let click = 0
|
||||
glossaryTerm.addEventListener('mouseover', () => {
|
||||
glossaryTerm.classList.add('active-term')
|
||||
showTooltip()
|
||||
tooltip.addEventListener('mouseover', showTooltip)
|
||||
tooltip.addEventListener('mouseout', hideTooltip)
|
||||
})
|
||||
glossaryTerm.addEventListener('mouseout', function (event) {
|
||||
click = 0
|
||||
hideTooltip()
|
||||
})
|
||||
glossaryTerm.addEventListener('click', function (event) {
|
||||
event.stopPropagation()
|
||||
if (click == 1) {
|
||||
hideTooltip()
|
||||
click = 0
|
||||
}
|
||||
else {
|
||||
showTooltip()
|
||||
click = 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
openOverlay()
|
||||
updatePointerEventsInPrivateSwiperCards()
|
||||
|
||||
window.addEventListener('scroll', function () {
|
||||
let activeTerm = document.querySelector('.active-term')
|
||||
if (activeTerm) {
|
||||
var tooltipId = activeTerm.getAttribute('data-glossary')
|
||||
var tooltip = document.getElementById(tooltipId)
|
||||
updateTooltipPosition(activeTerm, tooltip)
|
||||
}
|
||||
})
|
||||
|
||||
const glossaryTerms = document.querySelectorAll('.glossary-term')
|
||||
glossaryTerms.forEach(setupTooltip)
|
||||
})
|
||||
window.addEventListener('hashchange', openOverlay)
|
||||
|
||||
Reference in New Issue
Block a user