feat: better documentation styling

This commit is contained in:
afn 2022-11-27 18:22:17 -05:00
parent edd57a00be
commit ac2c6636a4
19 changed files with 460 additions and 127 deletions

View File

@ -1,4 +1,4 @@
@import url("https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800&display=swap");
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800&family=Source+Code+Pro:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
* {
box-sizing: inherit;
@ -34,6 +34,8 @@ body {
}
:root {
--main-font: "Manrope", sans-serif;
--mono-font: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
--white: #fff;
--accent-color: #9fd5ff;
--accent-color-two: hsl(207, 65%, 90%);

View File

@ -5,25 +5,51 @@
<!-- Always part of a list -->
<li>
<div class="doc-section">
<a href="/docs/{info.slug}">{info.title}</a>
<div class="doc-section item">
<a href="/docs/{info.slug}"><h5>{info.title}</h5></a>
</div>
</li>
<style>
a {
text-decoration: none;
background-color: inherit;
color: var(--white);
}
.doc-section {
background-color: var(--grey-one);
border-radius: 12px;
padding: 15px 20px;
}
a {
text-decoration: none;
}
li {
padding-bottom: 0.5rem;
}
li {
list-style: none;
}
.item {
padding: 0.6rem 1rem;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.6rem;
width: 100%;
user-select: none;
transition: background-color 0.4s var(--bezier-one);
}
.item h5 {
color: var(--grey-five);
transition: color 0.3s var(--bezier-one);
}
.selected h5 {
color: var(--grey-four);
transition: color 0.3s var(--bezier-one);
}
.selected {
background-color: var(--accent-color);
}
.item:hover:not(.selected) {
background-color: var(--grey-six);
}
.item:not(.selected):hover h5 {
color: var(--white);
}
</style>

View File

@ -1,30 +1,26 @@
<script lang="ts">
export let title: string;
export let searchTerm: string | null;
export let searchTermFiltered: string | null
import { fade } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
// import { onMount } from 'svelte';
// let search: HTMLInputElement;
function clear() {
searchTerm = null;
searchTermFiltered = null;
}
// function handleKeydown(event) {
// if (event.code === 'Slash') {
// search.focus;
// }
// }
</script>
<!-- <svelte:window on:keydown={handleKeydown} /> -->
<div>
<img src="../icons/search.svg" id="search" alt="Search" />
{#if searchTerm}
<img
src="../icons/close.svg"
id="clear"
alt="Close"
on:click={() => (searchTerm = null)}
on:keypress={() => (searchTerm = null)}
alt="Clear"
on:click={clear}
on:keypress={clear}
transition:fade|local={{ easing: quintOut, duration: 250 }}
/>
{/if}

View File

@ -1,39 +1,37 @@
<script lang="ts">
import { is_tree } from '$lib/documentation.shared';
import type { DocsTree } from '$lib/documentation.shared';
import { is_tree } from '$lib/documentation.shared';
import type { DocsTree } from '$lib/documentation.shared';
import DocsNavNode from '$lib/components/atoms/DocsNavNode.svelte';
import DocsNavNode from '$lib/components/atoms/DocsNavNode.svelte';
export let tree: DocsTree;
// How deeply nested this is.
export let nested = 0;
export let tree: DocsTree;
// How deeply nested this is.
export let nested = 0;
</script>
{#if nested}
<!-- The index should be part of the `ul` above us. -->
<DocsNavNode info={tree.index} />
<!-- The index should be part of the `ul` above us. -->
<DocsNavNode info={tree.index} />
{/if}
<ul>
{#if !nested}
<!-- There is no `ul` above us, so index should go here instead. -->
<DocsNavNode info={tree.index} />
{/if}
{#if !nested}
<!-- There is no `ul` above us, so index should go here instead. -->
<DocsNavNode info={tree.index} />
{/if}
{#each tree.nodes as node}
{#if is_tree(node)}
<!-- Recursion here is fine. We are not dealing with a tree the size of a linux root file system. -->
<svelte:self tree={node} nested={nested + 1} />
{:else}
<DocsNavNode info={node} />
{/if}
{/each}
{#each tree.nodes as node}
{#if is_tree(node)}
<!-- Recursion here is fine. We are not dealing with a tree the size of a linux root file system. -->
<svelte:self tree={node} nested={nested + 1} />
{:else}
<DocsNavNode info={node} />
{/if}
{/each}
</ul>
<style>
ul {
padding-left: 2rem;
list-style-type: "• ";
color: var(--white);
}
ul {
padding-left: 1rem;
}
</style>

View File

@ -1,29 +1,44 @@
#markup-content {
/* Defaults for text */
color: var(--white);
color: var(--accent-color-two);
font-weight: 300;
font-size: 1rem;
letter-spacing: 0.02rem;
padding: 100px 30px 0px 30px;
line-height: 1.75rem;
letter-spacing: 0.03rem !important;
a {
text-decoration: none;
color: var(--white);
border-bottom: 1.5px solid var(--accent-color);
padding: 0px 5px;
transition: all 0.4s var(--bezier-one);
color: var(--accent-color);
border-bottom: 1.5px solid var(--accent-low-opacity);
padding: 0px ;
}
a:hover {
background-color: var(--accent-color);
border-radius: 6px;
color: var(--grey-four);
}
code {
background-color: var(--grey-one);
border-radius: 8px;
padding: 0.2rem 0.5rem;
font-size: 0.8rem;
font-family: var(--mono-font);
font-weight: 300;
flex-wrap: wrap;
line-height: 1.25rem;
}
pre code {
font-size: 0.75rem;
background-color: var(--grey-six);
border-radius: 4px;
padding: 2px;
white-space: pre;
display: block;
flex-wrap: wrap;
padding: 0.5rem 1rem;
margin: 1rem 0;
}
h5 {
@ -34,9 +49,45 @@
color: var(--accent-color);
}
h1, h2, h3, h4, h5 {
margin-bottom: 1rem;
}
h1 {
font-size: 2.25rem;
font-weight: 600;
letter-spacing: -0.02rem;
color: var(--accent-color-two);
border-bottom: 1px solid var(--grey-three);
padding-bottom: 1rem;
margin-bottom: 1rem;
}
h2 {
font-size: 1.5rem;
letter-spacing: -0.02rem;
font-weight: 600;
color: var(--accent-color-two);
border-bottom: 1px solid var(--grey-three);
padding-bottom: 1rem;
margin-top: 2rem;
}
h3 {
margin-top: 1rem;
margin-bottom: 0.5rem;
}
li {
margin-left: 1rem;
margin-bottom: 0.5rem;
}
/* Markup processors output this for bold text, but css spec is goofy aah */
strong {
font-weight: bold;
letter-spacing: 0.01rem;
}
ul {

View File

@ -5,20 +5,28 @@
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import Footer from '$lib/components/molecules/Footer.svelte';
export let data: PageData;
</script>
<section id="doc-section-main" in:fly={{ y: 10, easing: quintOut, duration: 700 }}>
<section id="doc-section-main">
<div class="menu">
<DocsNavTree tree={data.tree} />
</div>
<slot />
</section>
<Footer />
<style lang="scss">
#doc-section-main {
margin-inline: auto;
width: min(90%, 90rem);
margin-top: 8rem;
margin-bottom: 5rem;
}
.menu {
padding: 90px 15px 0px 15px;
display: flex;
flex-direction: column;
gap: 1rem;
@ -26,9 +34,7 @@
#doc-section-main {
display: grid;
grid-template-columns: 300px 3fr;
height: 100vh;
width: 100%;
grid-template-columns: 320px 3fr;
gap: 3rem;
}
</style>

View File

@ -16,14 +16,6 @@
</svelte:head>
<div id="markup-content">
<h2 class="title">{data.title}</h2>
<h1 class="title">{data.title}</h1>
{@html data.content}
</div>
<style>
h2 {
color: var(--white);
line-height: 1.6;
}
</style>

View File

@ -21,10 +21,13 @@
const debounce = () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
searchTermFiltered = searchTerm?.replace(/\./g, '').replace(/\s/g, '').replace(/-/g, '').toLowerCase()
searchTermFiltered = searchTerm
?.replace(/\./g, '')
.replace(/\s/g, '')
.replace(/-/g, '')
.toLowerCase();
}, 500);
}
};
function search(patch: Patch) {
function checkPkgName(findTerm: string | boolean, array: any) {
@ -60,8 +63,8 @@
<main>
<aside in:fly={{ y: 10, easing: quintOut, duration: 750, delay: 100 }}>
<TreeMenu title="Search patches...">
<Search bind:searchTerm title="Search patches" on:keyup={debounce} />
<TreeMenu>
<Search bind:searchTerm bind:searchTermFiltered title="Search patches" on:keyup={debounce} />
<span>
{#each packages as pkg}
<TreeMenuButton bind:current name={pkg} />
@ -74,7 +77,7 @@
{#each patches as patch}
{#key current || searchTermFiltered}
{#if filterByPackage(current, patch.compatiblePackages) || !current}
{#if search(patch) || !searchTerm}
{#if search(patch) || !searchTermFiltered}
<div in:fly={{ x: 10, easing: quintOut, duration: 750, delay: 100 }}>
<PatchCell bind:current {patch} />
</div>

View File

@ -1,10 +0,0 @@
= AsciiDoc Example
= Another title
*Bold text*
== Section 1
* Item 1
* Item 2

View File

@ -1,4 +0,0 @@
# Nesting sample
# First one
Text

View File

@ -1,4 +0,0 @@
# Another Page
# Hello
Content!

View File

@ -1,4 +0,0 @@
# Nesting sample
# Second one
Text

View File

@ -1,4 +0,0 @@
# Nesting sample
# Third one
Text

View File

@ -0,0 +1,21 @@
# 🧩 Patching applications
1. Tap the `Patcher` button in the bottom navigation bar.
2. Tap the `Select an application` card.
3. Here, you have the option to choose between selecting an app from on-device or selecting an APK file directly from storage.
* For selecting on-device applications, tap on the desired app from the shown menu. For an app to show up in this list, the app must be already installed on the device.
> **Warning**: This feature is incomplete for non-root users as ReVanced has yet to add split APK support. We suggest you pick your APK from storage for now.
* To select from storage, click on the `Storage` button and select the APK normally through the file picker.
> **Note**: We recommend downloading APK files from [APKMirror](https://www.apkmirror.com/) as a temporary solution as `.apkm` and `.apks` files are not supported at the moment.
* Verify that the selected application is a version supported by ReVanced. [See the list of supported versions for apps here.](https://github.com/revanced/revanced-patches#-patches)
4. After selecting an application, you will be brought back to the `Patcher` screen again. Tap the `Select patches` card.
5. Select your desired patches. Note that certain patches are required for non-root installations. (e.g. *MicroG Support* patch for YouTube)
6. Tap the `Done` button.
7. After selecting your patches, you will be brought back to the `Patcher` screen again. Tap the `Patch` button.
8. The app is now patching. This process can range from 2-10 minutes depending on your device. Refrain from closing the Manager.
9. When patching is complete, tap on the `Install` button. You may also tap on the three vertical dots in the top right to share logs or the patched APK.
> **Warning**: If you close the Manager after patching, the patched APK file will be **automatically deleted from your device**!
## ⏭️ Whats next
The next section will guide you through managing your installed patched applications.
Continue: [🧰 Managing patched applications](2_managing-patched-applications.md)

View File

@ -0,0 +1,16 @@
# Prerequisites
##### docs/prerequisites
<br />
#### Requirements
- ADB
- x86/x86\_64 host architecture
- Zulu JDK 17
- Latest Android SDK if you plan to build the integrations from the source
- The APK file you want to patch (e.g. YouTube v17.36.37 or YouTube Music v5.23.50). If you want to mount patched applications as root, make sure the same version is installed on your device.
<br />
You can continue by either [building everything from source](https://github.com/revanced/revanced-documentation/wiki/Building-from-source) or [downloading the prebuilt packages](https://github.com/revanced/revanced-documentation/wiki/Downloading-prebuilt-packages).

View File

@ -1,4 +0,0 @@
# Markdown Example
Text
**Bold text**

View File

@ -0,0 +1,245 @@
# 🔎 Fingerprinting
Fingerprinting is the process of creating uniquely identifyable data about something arbitrarily large. In the context of ReVanced, fingerprinting is essential to be able to find classes, methods and fields without knowing their original names or certain other attributes, which would be used to identify them under normal circumstances.
## ⛳️ Example fingerprint
This page works with the following fingerprint as an example:
```kt
package app.revanced.patches.ads.fingerprints
// Imports
object LoadAdsFingerprint : MethodFingerprint(
returnType = "Z",
access = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Z"),
opcodes = listOf(Opcode.RETURN),
strings = listOf("pro"),
customFingerprint = { it.definingClass == "Lcom/some/app/ads/Loader;"}
)
```
## 🆗 Understanding the example fingerprint
The example fingerprint called `LoadAdsFingerprint` which extends on [`MethodFingerprint`](https://github.com/revanced/revanced-patcher/blob/main/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt#L28) is made to uniquely identify a certain method by capturing various attributes of the method such as the return type, access flags, an opcode pattern and more. The following code can be inferred just from the fingerprint:
```kt
package com.some.app.ads
// Imports
4 <attributes> class Loader {
5 public final Boolean <methodName>(<field>: Boolean) {
// ...
8 val userStatus = "pro";
// ...
12 return <returnValue>
}
}
```
## 🚀 How it works
Each attribute of the fingerprint is responsible to describe a specific but distinct part of the method. The combination out of those should be and ideally remain unique to all methods in all classes. In the case of the example fingerprint, the `customFingerprint` attribute is responsible to find the class the method is defined in. This greatly increases the uniqueness of the fingerprint, because now the possible methods reduce down to that class. Adding the signature of the method and a string the method implementation refers to in combination now creates a unique fingerprint in the current example:
- Package & class (Line 4)
```kt
customFingerprint = { it.definingClass == "Lcom/some/app/ads/Loader;"}
```
- Method signature (Line 5)
```kt
returnType = "Z",
access = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Z"),
```
- Method implementation (Line 8 & 12)
```kt
strings = listOf("pro"),
opcodes = listOf(Opcode.RETURN)
```
## 🔨 How to use fingerprints
After creating a fingerprint, add it to the constructor of the `BytecodePatch`:
```kt
class DisableAdsPatch : BytecodePatch(
listOf(LoadAdsFingerprint)
) { /* .. */ }
```
The ReVanced patcher will try to [resolve the fingerprint](https://github.com/revanced/revanced-patcher/blob/main/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt#L63) **before** it calls the `execute` method of the patch.
The fingerprint can now be used in the patch by accessing [`MethodFingerprint.result`](https://github.com/revanced/revanced-patcher/blob/d2f91a8545567429d64a1bcad6ca1dab62ec95bf/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt#L227):
```kt
class DisableAdsPatch : BytecodePatch(
listOf(LoadAdsFingerprint)
) {
override fun execute(context: BytecodeContext): PatchResult {
val result = LoadAdsFingerprint.result
?: return PatchResultError("LoadAdsFingerprint not found")
// ...
}
}
```
> **Note**: `MethodFingerprint.result` **can be null** if the fingerprint does not match any method. In such case, the fingerprint needs to be fixed and made more resilient if the error is caused by a later version of an app which the fingerprint was not tested on. A fingerprint is good, if it is _light_, but still resilient - like Carbon fiber-reinforced polymers.
If the fingerprint resolved to a method, the following properties are now available:
```kt
data class MethodFingerprintResult(
val method: Method,
val classDef: ClassDef,
val scanResult: MethodFingerprintScanResult,
// ...
) {
val mutableClass
val mutableMethod
// ...
}
```
> Details on how to use them in a patch and what exactly these are will be introduced properly later on this page.
## 🏹 Different ways to resolve a fingerprint
Usually, fingerprints are mostly resolved by the patcher, but it is also possible to manually resolve a fingerprint in a patch. This can be quite useful in lots of situations. To resolve a fingerprint you need a context to resolve it on. The context contains classes and thus methods to which the fingerprint can be resolved against. Example: _You have a fingerprint which you manually want to resolve **without** the help of the patcher._
> **Note**: A fingerprint should not be added to the constructor of `BytecodePatch` if manual resolution is intended, because the patcher would try resolve it before manual resolution.
- On a **list of classes** using [`MethodFingerprint.resolve`](https://github.com/revanced/revanced-patcher/blob/main/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt#L49)
This can be useful, if a fingerprint should be resolved to a smaller subset of classes, otherwise the fingerprint can be resolved by the patcher automatically.
```kt
class DisableAdsPatch : BytecodePatch(
/* listOf(LoadAdsFingerprint) */
) {
override fun execute(context: BytecodeContext): PatchResult {
val result = LoadAdsFingerprint.also { it.resolve(context, context.classes) }.result
?: return PatchResultError("LoadAdsFingerprint not found")
// ...
}
}
```
- On a **single class** using [`MethodFingerprint.resolve`](https://github.com/revanced/revanced-patcher/blob/main/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt#L63)
Sometimes you know a class but you need certain methods. In such case, you can resolve fingerprints on a class.
```kt
class DisableAdsPatch : BytecodePatch(
listOf(LoadAdsFingerprint)
) {
override fun execute(context: BytecodeContext): PatchResult {
val adsLoaderClass = context.classes.single { it.name == "Lcom/some/app/ads/Loader;" }
val result = LoadAdsFingerprint.also { it.resolve(context, adsLoaderClass) }.result
?: return PatchResultError("LoadAdsFingerprint not found")
// ...
}
}
```
- On a **method** using [`MethodFingerprint.resolve`](https://github.com/revanced/revanced-patcher/blob/main/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt#L78)
Resolving a fingerprint on a method is mostly only useful if the fingerprint is used to resolve certain information about a method such as `MethodFingerprintResult.scanResult`. Example: _A fingerprint should be used to resolve the method which loads ads. For that the fingerprint is added to the constructor of `BytecodePatch`. An additional fingerprint is responsible for finding the indices of the instructions with certain string references in the implementation of the method the first fingerprint resolved to._
```kt
class DisableAdsPatch : BytecodePatch(
/* listOf(LoadAdsFingerprint) */
) {
override fun execute(context: BytecodeContext): PatchResult {
// Make sure this fingerprint succeeds as the result is required
val adsFingerprintResult = LoadAdsFingerprint.result
?: return PatchResultError("LoadAdsFingerprint not found")
// Additional fingerprint to get the indices of two strings
val proStringsFingerprint = object : MethodFingerprint(
strings = listOf("free", "trial")
) {}
proStringsFingerprint.also {
// Resolve the fingerprint on the first fingerprints method
it.resolve(context, adsFingerprintResult.method)
}.result?.let { result ->
// Use the fingerprints result
result.scanResult.stringsScanResult!!.matches.forEach { match ->
println("The index of the string '${match.string}' is {match.index}")
}
} ?: return PatchResultError("pro strings fingerprint not found")
return PatchResultSuccess
}
}
```
## 🎯 The result of a fingerprint
After a `MethodFingerprint` resolves successfully, its result can be used. The result contains mutable and immutable references to the method and the class it is defined in.
> **Warning**: By default the immutable references **should be used** to prevent a mutable copy of the immutable references. For a patch to properly use a fingerprint though, usually write access is required. For that the mutable references can be used.
Among them, the result also contains [MethodFingerprintResult.scanResult](https://github.com/revanced/revanced-patcher/blob/main/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt#L239) which contains additional useful properties:
```kt
data class MethodFingerprintScanResult(
val patternScanResult: PatternScanResult?,
val stringsScanResult: StringsScanResult?
) {
data class PatternScanResult(
val startIndex: Int,
val endIndex: Int,
var warnings: List<Warning>? = null
)
data class StringsScanResult(val matches: List<StringMatch>){
data class StringMatch(val string: String, val index: Int)
}
// ...
}
```
The following properties are utilized by bytecode patches:
- The `MethodFingerprint.strings` allows patches to know the indices of the instructions which hold references to the strings.
- If a fingerprint defines `MethodFingerprint.opcodes`, the start and end index of the first instructions matching that pattern will be available. These are useful to patch the implementation of methods relative to the pattern. Ideally the pattern contains the instructions opcodes pattern which is to be patched, in order to guarantee a successfull patch.
> **Note**: Sometimes long patterns might be necessary, but the bigger the pattern list, the higher the chance it mutates if the app updates. For that reason the annotation `FuzzyPatternScanMethod` can be used on a fingerprint. The `FuzzyPatternScanMethod.threshold` will define, how many opcodes can remain unmatched. `PatternScanResult.warnings` can then be used if necessary, if it is necessary to know where pattern missmatches occured.
## ⭐ Closely related code examples
### 🧩 Patches
- [CommentsPatch](https://github.com/revanced/revanced-patches/blob/main/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/patch/CommentsPatch.kt)
- [MusicVideoAdsPatch](https://github.com/revanced/revanced-patches/blob/main/src/main/kotlin/app/revanced/patches/music/ad/video/patch/MusicVideoAdsPatch.kt)
### Fingerprints
- [LoadVideoAdsFingerprint](https://github.com/revanced/revanced-patches/blob/main/src/main/kotlin/app/revanced/patches/youtube/ad/video/fingerprints/LoadVideoAdsFingerprint.kt)
- [SeekbarTappingParentFingerprint](https://github.com/revanced/revanced-patches/blob/main/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingParentFingerprint.kt)
## ⏭️ Whats next
The next section will give a suggestion on coding conventions and on the file structure of a patch.
Continue: [📜 Patch file structure and conventions](4_structure_and_conventions.md)

View File

@ -0,0 +1,16 @@
# Prerequisites
##### docs/prerequisites
<br />
#### Requirements
- ADB
- x86/x86\_64 host architecture
- Zulu JDK 17
- Latest Android SDK if you plan to build the integrations from the source
- The APK file you want to patch (e.g. YouTube v17.36.37 or YouTube Music v5.23.50). If you want to mount patched applications as root, make sure the same version is installed on your device.
<br />
You can continue by either [building everything from source](https://github.com/revanced/revanced-documentation/wiki/Building-from-source) or [downloading the prebuilt packages](https://github.com/revanced/revanced-documentation/wiki/Downloading-prebuilt-packages).

View File

@ -1,9 +0,0 @@
# Writing documentation
You can write documentation with markdown or asciidoc.
## Title
The first line must be the `h1` equivalent of the markup language.
For markdown, this would be `# <title>` and `= <title>` for asciidoc.
**The title part may or may not go through the markup processor depending on the language.** It is instead added by the website itself.