diff --git a/package-lock.json b/package-lock.json index f089e6bb0c..0006ef1ab7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "classnames": "^2.3.2", "html-escaper": "^3.0.3", "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "nanostores": "^0.9.4", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -39,6 +40,7 @@ }, "devDependencies": { "@types/html-escaper": "^3.0.1", + "@types/lodash-es": "^4.17.10", "@types/node": "^20.8.9", "@types/react": "^18.2.32", "@types/react-dom": "^18.2.14", @@ -1892,6 +1894,21 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.30.tgz", "integrity": "sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==" }, + "node_modules/@types/lodash": { + "version": "4.14.200", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz", + "integrity": "sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==", + "dev": true + }, + "node_modules/@types/lodash-es": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.10.tgz", + "integrity": "sha512-YJP+w/2khSBwbUSFdGsSqmDvmnN3cCKoPOL7Zjle6s30ZtemkkqhjVfFqGwPN7ASil5VyjE2GtyU/yqYY6mC0A==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mdast": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz", diff --git a/package.json b/package.json index fd19aef6a5..4da02cb75c 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "classnames": "^2.3.2", "html-escaper": "^3.0.3", "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "nanostores": "^0.9.4", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -42,6 +43,7 @@ }, "devDependencies": { "@types/html-escaper": "^3.0.1", + "@types/lodash-es": "^4.17.10", "@types/node": "^20.8.9", "@types/react": "^18.2.32", "@types/react-dom": "^18.2.14", diff --git a/src/components/Header/SidebarToggle.tsx b/src/components/Header/SidebarToggle.tsx index 20ef875ee2..0fd230396c 100644 --- a/src/components/Header/SidebarToggle.tsx +++ b/src/components/Header/SidebarToggle.tsx @@ -1,3 +1,4 @@ +import classNames from "classnames"; import type { FC } from "react"; import { useState, useEffect } from "react"; @@ -16,7 +17,10 @@ const MenuToggle: FC = () => { return ( + + ); +}; + +export default CollapsedTOC; diff --git a/src/components/RightSidebar/TableOfContents/TOCMobile.tsx b/src/components/RightSidebar/TableOfContents/TOCMobile.tsx new file mode 100644 index 0000000000..7021aaf797 --- /dev/null +++ b/src/components/RightSidebar/TableOfContents/TOCMobile.tsx @@ -0,0 +1,39 @@ +import type { MarkdownHeading } from "astro"; +import type { FC } from "react"; +import { unescape } from "html-escaper"; + +import "../right-sidebar.css"; + +interface TableOfContentsMobileProps { + onThisPageID: string; + toc: React.RefObject; + headingsLocal: MarkdownHeading[]; +} + +const TableOfContentsMobile: FC = ({ + onThisPageID, + toc, + headingsLocal, +}) => { + return ( +
+

+ On this page +

+
    + {headingsLocal + .filter(({ depth }) => depth > 1 && depth < 4) + .map((heading) => ( +
  • + {unescape(heading.text)} +
  • + ))} +
+
+ ); +}; + +export default TableOfContentsMobile; diff --git a/src/components/RightSidebar/TableOfContents/index.tsx b/src/components/RightSidebar/TableOfContents/index.tsx new file mode 100644 index 0000000000..48a6a1904d --- /dev/null +++ b/src/components/RightSidebar/TableOfContents/index.tsx @@ -0,0 +1,150 @@ +import type { MarkdownHeading } from "astro"; +import type { FC } from "react"; +import { unescape } from "html-escaper"; +import { useState, useEffect, useRef } from "react"; +import classNames from "classnames"; +import { debounce } from "lodash-es"; + +import TableOfContentsMobile from "./TOCMobile"; +import CollapsedTOC from "./CollapsedTOC"; +import { getTocHeadings } from "@utils/helpers/toc/getTocHeadings"; +import { useScrollSpy } from "@hooks/useScrollSpy"; +import ChevronRight from "@components/Icons/react/ChevronRight"; + +import "../right-sidebar.css"; + +const HEADER_HEIGHT = 80; + +const checkIsMobile = () => { + const { innerWidth: width } = window; + return width <= 1024; +}; + +const TableOfContents: FC<{ headings: MarkdownHeading[]; title: string }> = ({ + headings = [], + title, +}) => { + const [headingsLocal, setHeadingsLocal] = useState(headings); + const [isMobile, setIsMobile] = useState(checkIsMobile()); + const toc = useRef(null); + const onThisPageID = "on-this-page-heading"; + const hashId = window.location?.hash?.replace("#", "") ?? ""; + const [clickedId, setClickedId] = useState(hashId); + + const [isOpened, setIsOpened] = useState(true); + + const { activeId } = useScrollSpy(); + + const handleClick = (id: string) => { + // need to scroll to the element without header height + setTimeout(() => { + window.scroll({ + top: window.scrollY + HEADER_HEIGHT, + behavior: "smooth", + }); + }, 50); + setClickedId(id); + }; + + useEffect(() => { + const headingsParsed = getTocHeadings(); + + setHeadingsLocal(headingsParsed); + }, []); + + useEffect(() => { + if (hashId) { + // required for the smooth scroll to the active header + setTimeout(() => { + document.getElementById(hashId)?.scrollIntoView({ + behavior: "smooth", + }); + }, 100); + } + }, []); + + const handleResize = () => { + setIsMobile(checkIsMobile()); + }; + + const resizeHandler = debounce(handleResize, 500); + + // required for the mobile design + useEffect(() => { + window.addEventListener("resize", resizeHandler); + + return () => window.removeEventListener("resize", resizeHandler); + }, []); + + // need to add right padding for the article when TOC is opened + useEffect(() => { + const article = document.getElementById("article-content"); + article!.className = + isOpened && !isMobile && headingsLocal.length + ? "article-content pr-[275px]" + : "article-content"; + }, [isOpened, isMobile]); + + if (!headingsLocal.length) { + return null; + } + + return ( + <> + {isOpened ? ( + + ) : ( + + )} + + + ); +}; + +export default TableOfContents; diff --git a/src/components/RightSidebar/right-sidebar.css b/src/components/RightSidebar/right-sidebar.css new file mode 100644 index 0000000000..697ac8f90e --- /dev/null +++ b/src/components/RightSidebar/right-sidebar.css @@ -0,0 +1,6 @@ +.toc-state-button { + &:hover svg { + background-color: #0b58fe; + color: white; + } +} diff --git a/src/content/docs/en/sdk/ios/configuration/deep-links/deep-link.mdx b/src/content/docs/en/sdk/ios/configuration/deep-links/deep-link.mdx index 5a1e3f1e6a..17bd91dc0c 100644 --- a/src/content/docs/en/sdk/ios/configuration/deep-links/deep-link.mdx +++ b/src/content/docs/en/sdk/ios/configuration/deep-links/deep-link.mdx @@ -1,23 +1,18 @@ --- -title: Set up deep links in Adjust -description: Configure your app in the Adjust dashboard to enable deep linking. +title: Enable deep links in Adjust +description: Configure your app in Adjust to enable deep linking. slug: en/sdk/ios/configuration/deep-links/deep-link sidebar-position: 2 --- -You need to configure your app in the Adjust dashboard to enable deep linking. To do this, make sure you have done the following: +You need to configure your app in Adjust to enable deep linking. To do this, make sure you have done the following: -- [ ] Added your app in the Adjust dashboard. +- [ ] [Added your app in Adjust](https://help.adjust.com/en/article/app-setup). - [ ] Retrieved all the required deep linking data. -Since iOS 9, Apple has changed the way you should handle deep linking in your apps. Depending on which iOS versions you are targeting, you need to set deep links to handle one or both of these methods. - -- iOS 9 and later - Universal links -- iOS 8 and earlier - Deep links with a custom URL scheme - ## Set up universal links in your app -To enable deep linking support for iOS 9 and later you need to set up universal links in the Adjust dashboard. +To enable deep linking support for iOS 9 and later you need to set up universal links in Adjust. @@ -25,27 +20,28 @@ You can enter only one Bundle ID per app. If you are testing an app with a Debug -Once you have gathered your setup data, you can add this to your app in the Adjust dashboard. Adding the information to your app enables you to add deep links to your campaigns. To set up universal links, follow these steps: - -1. Log in to the Adjust dashboard. -2. Navigate to your app and select your app options caret (). -3. Select All Settings --> Platforms. -4. Select iOS. - 1. If you haven't entered your Bundle ID, enter it in the IOS BUNDLE ID field. Make sure you enter the Release Bundle ID for your live app and the Debug Bundle ID for a debug app. -5. Select Universal Linking. -6. Enter your App Prefix. -7. Enter your App Scheme. If required, add your Release Custom URL Scheme or Debug Custom URL Scheme in the App Scheme field. -8. A raw universal link appears. You need this value to configure Associated Domains in Xcode. Example: `abcd.adj.st` -9. Select Save. -10. Select Save on the platform overview to save all settings. +Once you have gathered your setup data, you can add this to your app in Adjust. Adding the information to your app enables you to add deep links to your campaigns. To set up universal links, follow these steps: + +1. Go to AppView and select your app. +2. Ensure the iOS bundle ID is present. +3. Under Device type, choose your app's default device: + * Universal - iPhone and iPad + * iPhone + * iPad +4. Under Universal linking, turn on Enable universal linking to enable universal links for your app. + * Enter the App ID prefix. + * Enter the App scheme. +5. (Optional) Turn on Redirect all clicks to a custom URL and enter a Custom URL, if you want your users to go to a custom website instead of the App Store. This is recommended if your app doesn't have an App ID. +6. (Optional) Turn on Send data to App Store Connect and enter the Apple provider ID to send data to App Store Connect App Analytics. +7. Select Save. ## Set up deep links with a custom URL scheme -In this case, you need to pick a custom URL scheme name which your app will be responsible for opening. You can then use this scheme name in the Adjust tracker URL as part of the deeplink parameter. +In this case, you need to pick a custom URL scheme name which your app will be responsible for opening. You can then use this scheme name in the Adjust link as part of the deep link parameter. To create a deep link with a custom URL scheme, follow these steps: 1. Define the format of your custom URL scheme. If you are using a cross-platform framework, refer to the documentation for that framework to define the format of your custom URL scheme. Example: `example://summer-clothes?promo=beach` 2. URL encode the deep link. Example: `example%3A%2F%2Fsummer-clothes%3Fpromo%3Dbeach` -3. Pass this encoded deep link into an Adjust tracker URL. Example: `https://app.adjust.com/abc123?deeplink=%3A%2F%2Fsummer-clothes%3Fpromo%3Dbeach` +3. Pass this encoded deep link into an Adjust link. Example: `https://app.adjust.com/abc123?deeplink=%3A%2F%2Fsummer-clothes%3Fpromo%3Dbeach` 4. Append the `deeplink_js=1` parameter to the tracker URL with the encoded deep link. This forces the Adjust system to use the iOS custom URL scheme. Example: `https://app.adjust.com/abc123?deeplink_js=1&deeplink=%3A%2F%2Fsummer-clothes%3Fpromo%3Dbeach` diff --git a/src/hooks/useScrollSpy.ts b/src/hooks/useScrollSpy.ts new file mode 100644 index 0000000000..562d19f611 --- /dev/null +++ b/src/hooks/useScrollSpy.ts @@ -0,0 +1,30 @@ +import { useEffect, useState, useRef } from "react"; + +export const useScrollSpy = () => { + const observer = useRef(undefined); + const [activeId, setActiveId] = useState(""); + + useEffect(() => { + const handleObserver = (entries: IntersectionObserverEntry[]) => { + for (const entry of entries) { + if (entry?.isIntersecting) { + setActiveId(entry.target.id); + break; + } + } + }; + + observer.current = new IntersectionObserver(handleObserver, { + root: null, + rootMargin: "-100px 0px -66%", + threshold: 0, + }); + + const elements = document.querySelectorAll("h2, h3, h4"); + elements.forEach((elem) => observer.current?.observe(elem)); + + return () => observer.current?.disconnect(); + }, []); + + return { activeId }; +}; diff --git a/src/layouts/MainLayout.astro b/src/layouts/MainLayout.astro index f2bae06d53..48d22bf58d 100644 --- a/src/layouts/MainLayout.astro +++ b/src/layouts/MainLayout.astro @@ -11,7 +11,6 @@ import { SITE } from "../consts"; import { getNavigationEntries } from "src/utils/helpers/navigation/getNavigationEntries"; import SidebarHeader from "@components/LeftSidebar/SidebarHeader/SidebarHeader.astro"; import SidebarSearch from "@components/LeftSidebar/SidebarSearch/SidebarSearch.astro"; -import RightSidebar from "@components/RightSidebar/RightSidebar.astro"; import LanguageSwitch from "@components/LeftSidebar/LanguageSwitch"; import { LOCALE_NAMES, getLanguageFromURL } from "@i18n/locales"; import MobileHeader from "@components/Header/MobileHeader.astro"; @@ -65,16 +64,13 @@ const homeUrl = `${Astro.url.origin}/${currentLang}`;
- diff --git a/src/styles/index.css b/src/styles/index.css index 12b440e8be..d67dfad626 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -71,15 +71,17 @@ nav ul { } /* Typography */ -h1, -h2, -h3, -h4, -h5, -h6 { - margin-bottom: 1rem; - font-weight: bold; - line-height: 1; +.article-content { + & > h1, + h2, + h3, + h4, + h5, + h6 { + margin-bottom: 1rem; + font-weight: bold; + line-height: 1; + } } h1, @@ -99,20 +101,19 @@ h1 { @apply text-heading-1; } -h2 { - @apply text-heading-2; -} - -h3 { - @apply text-heading-3; -} - -h4 { - @apply text-heading-4; -} - -h5 { - @apply text-heading-5; +.article-content { + & > h2 { + @apply text-heading-2; + } + & > h3 { + @apply text-heading-3; + } + & > h4 { + @apply text-heading-4; + } + & > h5 { + @apply text-heading-5; + } } article p { @@ -147,34 +148,34 @@ nav a { color: inherit; } -article > section :is(ul, ol) > * + * { +.article-content :is(ul, ol) > * + * { margin-top: 0.75rem; } -article > section nav :is(ul, ol) > * + * { +.article-content nav :is(ul, ol) > * + * { margin-top: inherit; } -article > section li > :is(p, pre, blockquote):not(:first-child) { +.article-content li > :is(p, pre, blockquote):not(:first-child) { margin-top: 1rem; } -article > section :is(ul, ol) { +.article-content :is(ul, ol) { padding-left: 1em; } -article > section nav :is(ul, ol) { +.article-content nav :is(ul, ol) { padding-left: inherit; } -article ol { +.article-content ol { list-style: none; & li { position: relative; } } -article ol li::before { +.article-content ol li::before { content: counter(list-item); font-size: 15px; font-weight: 600; @@ -184,14 +185,14 @@ article ol li::before { left: -14px; } -article ul { +.article-content ul { list-style: none; & li { position: relative; } } -article ul li:not([role=tab])::before { +.article-content ul li:not(#tabs-container li)::before { content: "•"; font-weight: 500; position: absolute; @@ -200,17 +201,17 @@ article ul li:not([role=tab])::before { font-size: 20px; } -article > section nav { +.article-content nav { margin-top: 1rem; margin-bottom: 2rem; } -article > section ::marker { +.article-content ::marker { font-weight: bold; color: var(--theme-text-light); } -article > section iframe { +.article-content iframe { width: 100%; height: auto; aspect-ratio: 16 / 9; @@ -253,7 +254,7 @@ strong { /* Supporting Content */ -article .expressive-code { +.article-content .expressive-code { @apply mb-7; } @@ -350,7 +351,7 @@ img { max-width: 100%; } -button { +.article-content button { display: flex; align-items: center; justify-items: center; @@ -376,10 +377,8 @@ h2.heading { } .header-link { - font-size: 1em; - transition: border-inline-start-color 100ms ease-out, - background-color 200ms ease-out; - border-left: 4px solid var(--theme-divider); + font-size: 16px; + font-weight: 400; } .header-link a { @@ -400,16 +399,9 @@ h2.heading { } } -.header-link:hover, -.header-link:focus, -.header-link:focus-within { - border-inline-start-color: var(--theme-accent-secondary); -} - .header-link:hover a, .header-link a:focus { - color: var(--theme-text); - text-decoration: underline; + @apply hover:text-link-active; } .header-link svg { opacity: 0.6; @@ -419,14 +411,11 @@ h2.heading { } /* Add line and padding on the left side */ -.header-link { - padding-inline-start: 1rem; -} .header-link.depth-3 { - padding-inline-start: 2rem; + padding-inline-start: 1rem; } .header-link.depth-4 { - padding-inline-start: 3rem; + padding-inline-start: 2rem; } /* Screenreader Only Text */ @@ -460,9 +449,7 @@ h2.heading { /* Highlight TOC header link matching the current scroll position */ .current-header-link { - background-color: var(--theme-bg-accent); - /* Indicates the current heading for forced colors users in older browsers */ - outline: 1px solid transparent; + font-weight: 700; } @media (forced-colors: active) { diff --git a/src/utils/helpers/toc/getTocHeadings.ts b/src/utils/helpers/toc/getTocHeadings.ts new file mode 100644 index 0000000000..23cd739b4e --- /dev/null +++ b/src/utils/helpers/toc/getTocHeadings.ts @@ -0,0 +1,42 @@ +import type { MarkdownHeading } from "astro"; + +export const getTocHeadings = () => { + // this selector is more accurate cause we don`t need nested headers + const headings = document.querySelectorAll( + ".article-content > :is(h1, h2, h3, h4)" + ); + + const headingsParsed: MarkdownHeading[] = []; + const duplicates: { [key: string]: number } = {}; + // parsing data for the TOC + for (const heading of headings) { + const text = heading.textContent; + let slug = text + ?.toLocaleLowerCase() + .replace(/\W(? savedHeading.slug === slug + ); + + if (isSavedHeading) { + if (!duplicates[slug]) { + duplicates[slug] = 1; + } else { + duplicates[slug] = duplicates[slug] + 1; + } + + slug = `${slug}-${duplicates[slug]}`; + } + + headingsParsed.push({ + depth: +heading.tagName.replace("H", ""), + slug, + text: text, + } as MarkdownHeading); + } + + return headingsParsed; +}; diff --git a/tsconfig.json b/tsconfig.json index 8745080fc7..24df3cfeb5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,31 +12,16 @@ "@store/*": ["src/store/*"], "@content/*": ["src/content/docs/*"], "@hooks/*": ["src/hooks/*"], + "@utils/*": ["src/utils/*"], "@i18n/*": ["src/i18n/*"], - "@ios-examples/*": [ - "src/code-snippets/ios/examples/*" - ], - "@ios-signatures/*": [ - "src/code-snippets/ios/signatures/*" - ], - "@unity-examples/*": [ - "src/code-snippets/unity/examples/*" - ], - "@unity-signatures/*": [ - "src/code-snippets/unity/signatures/*" - ], - "@web-examples/*": [ - "src/code-snippets/web/examples/*" - ], - "@web-signatures/*": [ - "src/code-snippets/web/signatures/*" - ], - "@android-examples/*": [ - "src/code-snippets/android/examples/*" - ], - "@android-signatures/*": [ - "src/code-snippets/android/signatures/*" - ] + "@android-examples/*": ["src/code-snippets/android/examples/*"], + "@android-signatures/*": ["src/code-snippets/android/signatures/*"], + "@ios-examples/*": ["src/code-snippets/ios/examples/*"], + "@ios-signatures/*": ["src/code-snippets/ios/signatures/*"], + "@unity-examples/*": ["src/code-snippets/unity/examples/*"], + "@unity-signatures/*": ["src/code-snippets/unity/signatures/*"], + "@web-examples/*": ["src/code-snippets/web/examples/*"], + "@web-signatures/*": ["src/code-snippets/web/signatures/*"] } } }