Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
356a06e694 | ||
|
|
fce516a76f | ||
|
|
42ade0fbd1 | ||
|
|
ba07f5dad4 | ||
|
|
cc89e0f3f1 | ||
|
|
0e14d3f9c1 | ||
|
|
6716e2277d | ||
|
|
f986dc5d11 | ||
|
|
570a4096f9 | ||
|
|
d302795512 | ||
|
|
115acce80c | ||
|
|
a8731b0ca2 | ||
|
|
fd596bf418 | ||
|
|
ef7cfffeb6 | ||
|
|
ac9bcabd9c | ||
|
|
5bb961f16b | ||
|
|
a46677832b | ||
|
|
624abf0df4 | ||
|
|
e81a4ade97 | ||
|
|
9708685506 | ||
|
|
2713f0e610 | ||
|
|
3b8963fad0 | ||
|
|
060682a1ac | ||
|
|
95cd48571e | ||
|
|
3d619e6a98 |
9
.github/workflows/app-release.yml
vendored
9
.github/workflows/app-release.yml
vendored
@@ -41,9 +41,6 @@ jobs:
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Install Tauri CLI
|
||||
run: cargo install tauri-cli --git https://github.com/modrinth/tauri.git --rev 5e2942876c2266594ed1db516c1d9975c873c36a
|
||||
|
||||
- name: Setup rust cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -65,7 +62,7 @@ jobs:
|
||||
!target/release/bundle/msi/*.msi
|
||||
!target/release/bundle/msi/*.msi.zip
|
||||
!target/release/bundle/msi/*.msi.zip.sig
|
||||
|
||||
|
||||
!target/release/bundle/nsis/*.exe
|
||||
!target/release/bundle/nsis/*.nsis.zip
|
||||
!target/release/bundle/nsis/*.nsis.zip.sig
|
||||
@@ -121,7 +118,6 @@ jobs:
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
V1_COMPATIBLE_BIN_NAME: true
|
||||
|
||||
- name: build app
|
||||
run: pnpm --filter=@modrinth/app run tauri build --config "tauri-release.conf.json"
|
||||
@@ -131,7 +127,6 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
V1_COMPATIBLE_BIN_NAME: true
|
||||
|
||||
- name: upload ${{ matrix.platform }}
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -154,7 +149,7 @@ jobs:
|
||||
target/release/bundle/msi/*.msi
|
||||
target/release/bundle/msi/*.msi.zip
|
||||
target/release/bundle/msi/*.msi.zip.sig
|
||||
|
||||
|
||||
target/release/bundle/nsis/*.exe
|
||||
target/release/bundle/nsis/*.nsis.zip
|
||||
target/release/bundle/nsis/*.nsis.zip.sig
|
||||
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -32,9 +32,6 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Install Tauri CLI
|
||||
run: cargo install tauri-cli --git https://github.com/modrinth/tauri.git --rev 5e2942876c2266594ed1db516c1d9975c873c36a
|
||||
|
||||
- name: Setup Node.JS environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
|
||||
505
Cargo.lock
generated
505
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -18,5 +18,4 @@ strip = true # Remove debug symbols
|
||||
opt-level = 3
|
||||
|
||||
[patch.crates-io]
|
||||
wry = { git = "https://github.com/modrinth/wry", rev = "5840108" }
|
||||
tauri = { git = "https://github.com/modrinth/tauri", rev = "5e29428" }
|
||||
wry = { git = "https://github.com/modrinth/wry", rev = "27fb16b" }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@modrinth/app-frontend",
|
||||
"private": true,
|
||||
"version": "0.8.7",
|
||||
"version": "0.8.9",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -230,7 +230,7 @@ async function repairInstance() {
|
||||
</p>
|
||||
<p>You may be able to fix it through one of the following ways:</p>
|
||||
<ul>
|
||||
<li>Ennsuring you are connected to the internet, then try restarting the app.</li>
|
||||
<li>Ensuring you are connected to the internet, then try restarting the app.</li>
|
||||
<li>Redownloading the app.</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
@@ -218,7 +218,6 @@ import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { install_from_file } from '@/helpers/pack.js'
|
||||
import {
|
||||
get_default_launcher_path,
|
||||
@@ -226,6 +225,7 @@ import {
|
||||
import_instance,
|
||||
} from '@/helpers/import.js'
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||
|
||||
const profile_name = ref('')
|
||||
const game_version = ref('')
|
||||
@@ -255,13 +255,15 @@ defineExpose({
|
||||
isShowing.value = true
|
||||
modal.value.show()
|
||||
|
||||
unlistener.value = await listen('tauri://file-drop', async (event) => {
|
||||
unlistener.value = await getCurrentWebview().onDragDropEvent(async (event) => {
|
||||
// Only if modal is showing
|
||||
if (!isShowing.value) return
|
||||
if (event.payload.type !== 'drop') return
|
||||
if (creationType.value !== 'from file') return
|
||||
hide()
|
||||
if (event.payload && event.payload.length > 0 && event.payload[0].endsWith('.mrpack')) {
|
||||
await install_from_file(event.payload[0]).catch(handleError)
|
||||
const { paths } = event.payload
|
||||
if (paths && paths.length > 0 && paths[0].endsWith('.mrpack')) {
|
||||
await install_from_file(paths[0]).catch(handleError)
|
||||
trackEvent('InstanceCreate', {
|
||||
source: 'CreationModalFileDrop',
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import { get as getCreds } from '@/helpers/mr_auth.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { get_user } from '@/helpers/cache.js'
|
||||
import { ChevronRightIcon } from '@modrinth/assets'
|
||||
import { init_ads_window } from '@/helpers/ads.js'
|
||||
import { init_ads_window, open_ads_link, record_ads_click } from '@/helpers/ads.js'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
|
||||
const showAd = ref(true)
|
||||
@@ -73,6 +73,11 @@ function updateAdPosition(overrideShown = false) {
|
||||
}
|
||||
}
|
||||
|
||||
async function openPlusLink() {
|
||||
await record_ads_click()
|
||||
await open_ads_link('https://modrinth.com/plus', 'https://modrinth.com')
|
||||
}
|
||||
|
||||
const unlisten = await listen('ads-scroll', (event) => {
|
||||
if (adsWrapper.value) {
|
||||
adsWrapper.value.parentNode.scrollTop += event.payload.scroll
|
||||
@@ -105,17 +110,17 @@ onUnmounted(() => {
|
||||
class="ad-parent relative mb-3 flex w-full justify-center rounded-2xl bg-bg-raised cursor-pointer"
|
||||
>
|
||||
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
|
||||
<p class="m-0 text-2xl font-bold text-contrast">90% of ad revenue goes to creators</p>
|
||||
<a
|
||||
href="https://modrinth.com/plus"
|
||||
class="mt-auto items-center gap-1 text-purple hover:underline"
|
||||
<p class="m-0 text-2xl font-bold text-contrast">75% of ad revenue goes to creators</p>
|
||||
<button
|
||||
class="mt-auto items-center gap-1 text-purple hover:underline bg-transparent border-none text-left cursor-pointer outline-none"
|
||||
@click="openPlusLink"
|
||||
>
|
||||
<span>
|
||||
Support creators and Modrinth ad-free with
|
||||
<span class="font-bold">Modrinth+</span>
|
||||
</span>
|
||||
<ChevronRightIcon class="relative top-[3px] h-5 w-5" />
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -230,7 +230,7 @@ const createInstance = async () => {
|
||||
@click="installModal.hide()"
|
||||
>
|
||||
<Avatar
|
||||
:src="profile.icon_path ? tauri.convertFileSrc(profile.icon_path) : null"
|
||||
:src="profile.icon_path ? convertFileSrc(profile.icon_path) : null"
|
||||
class="profile-image"
|
||||
/>
|
||||
{{ profile.name }}
|
||||
|
||||
@@ -11,3 +11,11 @@ export async function show_ads_window() {
|
||||
export async function hide_ads_window(reset) {
|
||||
return await invoke('plugin:ads|hide_ads_window', { reset })
|
||||
}
|
||||
|
||||
export async function record_ads_click() {
|
||||
return await invoke('plugin:ads|record_ads_click')
|
||||
}
|
||||
|
||||
export async function open_ads_link(path, origin) {
|
||||
return await invoke('plugin:ads|open_link', { path, origin })
|
||||
}
|
||||
|
||||
@@ -363,6 +363,25 @@ async function purgeCache() {
|
||||
<span class="label__title size-card-header">Privacy</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="opt-out-analytics">
|
||||
<span class="label__title">Personalized ads</span>
|
||||
<span class="label__description">
|
||||
Modrinth's ad provider, Aditude, shows ads based on your preferences. By disabling this
|
||||
option, you opt out and ads will no longer be shown based on your interests.
|
||||
</span>
|
||||
</label>
|
||||
<Toggle
|
||||
id="opt-out-analytics"
|
||||
:model-value="settings.personalized_ads"
|
||||
:checked="settings.personalized_ads"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
settings.personalized_ads = e
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="opt-out-analytics">
|
||||
<span class="label__title">Telemetry</span>
|
||||
|
||||
@@ -378,7 +378,6 @@ import {
|
||||
} from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { highlightModInProfile } from '@/helpers/utils.js'
|
||||
import { MenuIcon, ToggleIcon, TextInputIcon, AddProjectImage, PackageIcon } from '@/assets/icons'
|
||||
import ExportModal from '@/components/ui/ExportModal.vue'
|
||||
@@ -393,6 +392,7 @@ import {
|
||||
import { profile_listener } from '@/helpers/events.js'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
@@ -879,8 +879,10 @@ async function refreshProjects() {
|
||||
refreshingProjects.value = false
|
||||
}
|
||||
|
||||
const unlisten = await listen('tauri://file-drop', async (event) => {
|
||||
for (const file of event.payload) {
|
||||
const unlisten = await getCurrentWebview().onDragDropEvent(async (event) => {
|
||||
if (event.payload.type !== 'drop') return
|
||||
|
||||
for (const file of event.payload.paths) {
|
||||
if (file.endsWith('.mrpack')) continue
|
||||
await add_project_from_path(props.instance.path, file).catch(handleError)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "theseus_gui"
|
||||
version = "0.8.7"
|
||||
version = "0.8.9"
|
||||
description = "The Modrinth App is a desktop application for managing your Minecraft mods"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/modrinth/code/apps/app/"
|
||||
@@ -8,7 +8,7 @@ edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { git = "https://github.com/modrinth/tauri", features = ["codegen"], rev = "5e29428" }
|
||||
tauri-build = { version = "2.0.0-rc", features = ["codegen"] }
|
||||
|
||||
[dependencies]
|
||||
theseus = { path = "../../packages/app-lib", features = ["tauri"] }
|
||||
@@ -16,7 +16,7 @@ theseus = { path = "../../packages/app-lib", features = ["tauri"] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
tauri = { git = "https://github.com/modrinth/tauri", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"], rev = "5e29428" }
|
||||
tauri = { version = "2.0.0-rc", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
|
||||
tauri-plugin-window-state = "2.0.0-rc"
|
||||
tauri-plugin-deep-link = "2.0.0-rc"
|
||||
tauri-plugin-os = "2.0.0-rc"
|
||||
|
||||
@@ -22,7 +22,6 @@ Before you begin, ensure you have the following installed on your machine:
|
||||
Follow these steps to set up your development environment:
|
||||
|
||||
```bash
|
||||
cargo install tauri-cli --git https://github.com/modrinth/tauri.git --rev 5e2942876c2266594ed1db516c1d9975c873c36a
|
||||
pnpm install
|
||||
pnpm app:dev
|
||||
```
|
||||
|
||||
@@ -226,6 +226,9 @@ fn main() {
|
||||
"hide_ads_window",
|
||||
"scroll_ads_window",
|
||||
"show_ads_window",
|
||||
"record_ads_click",
|
||||
"open_link",
|
||||
"get_ads_personalization",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"ads-window"
|
||||
],
|
||||
"permissions": [
|
||||
"shell:allow-open",
|
||||
"ads:default"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"ads":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["shell:allow-open","ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]},"updater":{"identifier":"updater","description":"","local":true,"windows":["main"],"permissions":["updater:default"]}}
|
||||
{"ads":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]},"updater":{"identifier":"updater","description":"","local":true,"windows":["main"],"permissions":["updater:default"]}}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,346 +0,0 @@
|
||||
<?if $(sys.BUILDARCH)="x86"?>
|
||||
<?define Win64 = "no" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?elseif $(sys.BUILDARCH)="x64"?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else?>
|
||||
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
|
||||
<?endif?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product
|
||||
Id="*"
|
||||
Name="{{product_name}}"
|
||||
UpgradeCode="{{upgrade_code}}"
|
||||
Language="!(loc.TauriLanguage)"
|
||||
Manufacturer="{{manufacturer}}"
|
||||
Version="{{version}}">
|
||||
|
||||
<Package Id="*"
|
||||
Keywords="Installer"
|
||||
InstallerVersion="450"
|
||||
Languages="0"
|
||||
Compressed="yes"
|
||||
InstallScope="perMachine"
|
||||
SummaryCodepage="!(loc.TauriCodepage)"/>
|
||||
|
||||
<!-- https://docs.microsoft.com/en-us/windows/win32/msi/reinstallmode -->
|
||||
<!-- reinstall all files; rewrite all registry entries; reinstall all shortcuts -->
|
||||
<Property Id="REINSTALLMODE" Value="amus" />
|
||||
|
||||
{{#if allow_downgrades}}
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes" />
|
||||
{{else}}
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" AllowSameVersionUpgrades="yes" />
|
||||
{{/if}}
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<Media Id="1" Cabinet="app.cab" EmbedCab="yes" />
|
||||
|
||||
{{#if banner_path}}
|
||||
<WixVariable Id="WixUIBannerBmp" Value="{{banner_path}}" />
|
||||
{{/if}}
|
||||
{{#if dialog_image_path}}
|
||||
<WixVariable Id="WixUIDialogBmp" Value="{{dialog_image_path}}" />
|
||||
{{/if}}
|
||||
{{#if license}}
|
||||
<WixVariable Id="WixUILicenseRtf" Value="{{license}}" />
|
||||
{{/if}}
|
||||
|
||||
<Icon Id="ProductIcon" SourceFile="{{icon_path}}"/>
|
||||
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />
|
||||
<Property Id="ARPNOREPAIR" Value="yes" Secure="yes" /> <!-- Remove repair -->
|
||||
<SetProperty Id="ARPNOMODIFY" Value="1" After="InstallValidate" Sequence="execute"/>
|
||||
|
||||
<!-- initialize with previous InstallDir -->
|
||||
<Property Id="INSTALLDIR">
|
||||
<RegistrySearch Id="PrevInstallDirReg" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw"/>
|
||||
</Property>
|
||||
|
||||
<!-- launch app checkbox -->
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="!(loc.LaunchApp)" />
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
|
||||
<Property Id="WixShellExecTarget" Value="[!Path]" />
|
||||
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
|
||||
|
||||
<UI>
|
||||
<!-- launch app checkbox -->
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
|
||||
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
|
||||
|
||||
{{#unless license}}
|
||||
<!-- Skip license dialog -->
|
||||
<Publish Dialog="WelcomeDlg"
|
||||
Control="Next"
|
||||
Event="NewDialog"
|
||||
Value="InstallDirDlg"
|
||||
Order="2">1</Publish>
|
||||
<Publish Dialog="InstallDirDlg"
|
||||
Control="Back"
|
||||
Event="NewDialog"
|
||||
Value="WelcomeDlg"
|
||||
Order="2">1</Publish>
|
||||
{{/unless}}
|
||||
</UI>
|
||||
|
||||
<UIRef Id="WixUI_InstallDir" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="DesktopFolder" Name="Desktop">
|
||||
<Component Id="ApplicationShortcutDesktop" Guid="*">
|
||||
<Shortcut Id="ApplicationDesktopShortcut" Name="{{product_name}}" Description="Runs {{product_name}}" Target="[!Path]" WorkingDirectory="INSTALLDIR" />
|
||||
<RemoveFolder Id="DesktopFolder" On="uninstall" />
|
||||
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Desktop Shortcut" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</Directory>
|
||||
<Directory Id="$(var.PlatformProgramFilesFolder)" Name="PFiles">
|
||||
<Directory Id="INSTALLDIR" Name="{{product_name}}"/>
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Directory Id="ApplicationProgramsFolder" Name="{{product_name}}"/>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<DirectoryRef Id="INSTALLDIR">
|
||||
<Component Id="RegistryEntries" Guid="*">
|
||||
<RegistryKey Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}">
|
||||
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLDIR]" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<Component Id="Path" Guid="{{path_component_guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Path" Source="{{app_exe_source}}" KeyPath="yes" Checksum="yes"/>
|
||||
<!-- THESEUS -->
|
||||
<ProgId Id="theseus.mrpack.Document" Description="Modrinth File">
|
||||
<Extension Id="mrpack" ContentType="application/mrpack">
|
||||
<!-- no flags on argument, so we can hijack deep link library-->
|
||||
<Verb Id="open" Command="Open" TargetFile="Path" Argument=""%1"" />
|
||||
</Extension>
|
||||
</ProgId>
|
||||
<!-- /THESEUS -->
|
||||
</Component>
|
||||
{{#each binaries as |bin| ~}}
|
||||
<Component Id="{{ bin.id }}" Guid="{{bin.guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Bin_{{ bin.id }}" Source="{{bin.path}}" KeyPath="yes"/>
|
||||
</Component>
|
||||
{{/each~}}
|
||||
{{#if enable_elevated_update_task}}
|
||||
<Component Id="UpdateTask" Guid="C492327D-9720-4CD5-8DB8-F09082AF44BE" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTask" Source="update.xml" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="UpdateTaskInstaller" Guid="011F25ED-9BE3-50A7-9E9B-3519ED2B9932" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTaskInstaller" Source="install-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="UpdateTaskUninstaller" Guid="D4F6CC3F-32DC-5FD0-95E8-782FFD7BBCE1" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTaskUninstaller" Source="uninstall-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
{{/if}}
|
||||
{{resources}}
|
||||
<Component Id="CMP_UninstallShortcut" Guid="*">
|
||||
|
||||
<Shortcut Id="UninstallShortcut"
|
||||
Name="Uninstall {{product_name}}"
|
||||
Description="Uninstalls {{product_name}}"
|
||||
Target="[System64Folder]msiexec.exe"
|
||||
Arguments="/x [ProductCode]" />
|
||||
|
||||
<RemoveFolder Id="INSTALLDIR"
|
||||
On="uninstall" />
|
||||
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\\{{manufacturer}}\\{{product_name}}"
|
||||
Name="Uninstaller Shortcut"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<!-- THESEUS -->
|
||||
<Component Id="FileTypeAssociationsReg" Guid="*">
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities" Name="ApplicationDescription" Value="theseus" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities" Name="ApplicationIcon" Value="[INSTALLDIR]theseus,0" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities" Name="ApplicationName" Value="theseus" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\DefaultIcon" Value="[INSTALLDIR]theseus,1" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\FileAssociations" Name=".mrpack" Value="theseus.mrpack.Document" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\MIMEAssociations" Name="application/mrpack" Value="theseus.mrpack.Document" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\shell\Open\command" Value=""[INSTALLDIR]theseus" -e "%1"" Type="string" />
|
||||
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\RegisteredApplications" Name="theseus" Value="SOFTWARE\modrinth\theseus\Capabilities" Type="string" KeyPath="yes" />
|
||||
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\theseus.mrpack.Document" Name="MRPACK File" Value="Modrinth Modpack Installer" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\.mrpack" Name="Content Type" Value="application/mrpack" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\.mrpack\OpenWithList\theseus" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\.mrpack\OpenWithProgids" Name="theseus.mrpack.Document" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\mrpack\SupportedTypes" Name=".mrpack" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\mrpack\shell\open" Name="FriendlyAppName" Value="theseus" Type="string" />
|
||||
</Component>
|
||||
<!-- /THESEUS -->
|
||||
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||
<Component Id="ApplicationShortcut" Guid="*">
|
||||
<Shortcut Id="ApplicationStartMenuShortcut"
|
||||
Name="{{product_name}}"
|
||||
Description="Runs {{product_name}}"
|
||||
Target="[!Path]"
|
||||
Icon="ProductIcon"
|
||||
WorkingDirectory="INSTALLDIR">
|
||||
<ShortcutProperty Key="System.AppUserModel.ID" Value="{{bundle_id}}"/>
|
||||
</Shortcut>
|
||||
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
|
||||
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Start Menu Shortcut" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
{{#each merge_modules as |msm| ~}}
|
||||
<DirectoryRef Id="TARGETDIR">
|
||||
<Merge Id="{{ msm.name }}" SourceFile="{{ msm.path }}" DiskId="1" Language="!(loc.TauriLanguage)" />
|
||||
</DirectoryRef>
|
||||
|
||||
<Feature Id="{{ msm.name }}" Title="{{ msm.name }}" AllowAdvertise="no" Display="hidden" Level="1">
|
||||
<MergeRef Id="{{ msm.name }}"/>
|
||||
</Feature>
|
||||
{{/each~}}
|
||||
|
||||
<Feature
|
||||
Id="MainProgram"
|
||||
Title="Application"
|
||||
Description="!(loc.InstallAppFeature)"
|
||||
Level="1"
|
||||
ConfigurableDirectory="INSTALLDIR"
|
||||
AllowAdvertise="no"
|
||||
Display="expand"
|
||||
Absent="disallow">
|
||||
|
||||
<ComponentRef Id="RegistryEntries"/>
|
||||
|
||||
<!-- THESEUS -->
|
||||
<ComponentRef Id="FileTypeAssociationsReg" />
|
||||
<!-- /THESEUS -->
|
||||
|
||||
{{#each resource_file_ids as |resource_file_id| ~}}
|
||||
<ComponentRef Id="{{ resource_file_id }}"/>
|
||||
{{/each~}}
|
||||
|
||||
{{#if enable_elevated_update_task}}
|
||||
<ComponentRef Id="UpdateTask" />
|
||||
<ComponentRef Id="UpdateTaskInstaller" />
|
||||
<ComponentRef Id="UpdateTaskUninstaller" />
|
||||
{{/if}}
|
||||
|
||||
<Feature Id="ShortcutsFeature"
|
||||
Title="Shortcuts"
|
||||
Level="1">
|
||||
<ComponentRef Id="Path"/>
|
||||
<ComponentRef Id="CMP_UninstallShortcut" />
|
||||
<ComponentRef Id="ApplicationShortcut" />
|
||||
<ComponentRef Id="ApplicationShortcutDesktop" />
|
||||
</Feature>
|
||||
|
||||
<Feature
|
||||
Id="Environment"
|
||||
Title="PATH Environment Variable"
|
||||
Description="!(loc.PathEnvVarFeature)"
|
||||
Level="1"
|
||||
Absent="allow">
|
||||
<ComponentRef Id="Path"/>
|
||||
{{#each binaries as |bin| ~}}
|
||||
<ComponentRef Id="{{ bin.id }}"/>
|
||||
{{/each~}}
|
||||
</Feature>
|
||||
</Feature>
|
||||
|
||||
<Feature Id="External" AllowAdvertise="no" Absent="disallow">
|
||||
{{#each component_group_refs as |id| ~}}
|
||||
<ComponentGroupRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each component_refs as |id| ~}}
|
||||
<ComponentRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each feature_group_refs as |id| ~}}
|
||||
<FeatureGroupRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each feature_refs as |id| ~}}
|
||||
<FeatureRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each merge_refs as |id| ~}}
|
||||
<MergeRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
</Feature>
|
||||
|
||||
{{#if install_webview}}
|
||||
<!-- WebView2 -->
|
||||
<Property Id="WVRTINSTALLED">
|
||||
<RegistrySearch Id="WVRTInstalledSystem" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" Win64="no" />
|
||||
<RegistrySearch Id="WVRTInstalledUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
|
||||
</Property>
|
||||
|
||||
{{#if download_bootstrapper}}
|
||||
<CustomAction Id='DownloadAndInvokeBootstrapper' Directory="INSTALLDIR" Execute="deferred" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\{] [\[]Net.ServicePointManager[\]]::SecurityProtocol = [\[]Net.SecurityProtocolType[\]]::Tls12 [\}] catch [\{][\}]; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ({{webview_installer_args}} '/install') -Wait' Return='check'/>
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded webview bootstrapper mode -->
|
||||
{{#if webview2_bootstrapper_path}}
|
||||
<Binary Id="MicrosoftEdgeWebview2Setup.exe" SourceFile="{{webview2_bootstrapper_path}}"/>
|
||||
<CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded offline installer -->
|
||||
{{#if webview2_installer_path}}
|
||||
<Binary Id="MicrosoftEdgeWebView2RuntimeInstaller.exe" SourceFile="{{webview2_installer_path}}"/>
|
||||
<CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeStandalone' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if enable_elevated_update_task}}
|
||||
<!-- Install an elevated update task within Windows Task Scheduler -->
|
||||
<CustomAction
|
||||
Id="CreateUpdateTask"
|
||||
Return="check"
|
||||
Directory="INSTALLDIR"
|
||||
Execute="commit"
|
||||
Impersonate="yes"
|
||||
ExeCommand="powershell.exe -WindowStyle hidden .\install-task.ps1" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='CreateUpdateTask' Before='InstallFinalize'>
|
||||
NOT(REMOVE)
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
<!-- Remove elevated update task during uninstall -->
|
||||
<CustomAction
|
||||
Id="DeleteUpdateTask"
|
||||
Return="check"
|
||||
Directory="INSTALLDIR"
|
||||
ExeCommand="powershell.exe -WindowStyle hidden .\uninstall-task.ps1" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="DeleteUpdateTask" Before='InstallFinalize'>
|
||||
(REMOVE = "ALL") AND NOT UPGRADINGPRODUCTCODE
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLDIR]" After="CostFinalize"/>
|
||||
</Product>
|
||||
</Wix>
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "@modrinth/app",
|
||||
"scripts": {
|
||||
"build": "cargo tauri build",
|
||||
"tauri": "cargo tauri",
|
||||
"dev": "cargo tauri dev",
|
||||
"build": "tauri build",
|
||||
"tauri": "tauri",
|
||||
"dev": "tauri dev",
|
||||
"test": "cargo test",
|
||||
"lint": "cargo fmt --check && cargo clippy -- -D warnings",
|
||||
"fix": "cargo fmt && cargo clippy --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.0.0-rc.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modrinth/app-frontend": "workspace:*",
|
||||
"@modrinth/app-lib": "workspace:*"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
if (!window.modrinthClickListener) {
|
||||
window.modrinthClickListener = true
|
||||
document.addEventListener('click', function (e) {
|
||||
document.addEventListener(
|
||||
'click',
|
||||
function (e) {
|
||||
window.top.postMessage({ modrinthAdClick: true }, 'https://modrinth.com')
|
||||
|
||||
let target = e.target
|
||||
while (target != null) {
|
||||
if (target.matches('a')) {
|
||||
@@ -12,8 +14,9 @@ if (!window.modrinthClickListener) {
|
||||
}
|
||||
target = target.parentElement
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
true,
|
||||
)
|
||||
|
||||
window.open = (url, target, features) => {
|
||||
window.top.postMessage({ modrinthOpenUrl: url }, 'https://modrinth.com')
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
use serde::Serialize;
|
||||
use std::collections::HashSet;
|
||||
use std::time::{Duration, Instant};
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{Emitter, LogicalPosition, LogicalSize, Manager, Runtime};
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
use theseus::settings;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct AdsState {
|
||||
pub shown: bool,
|
||||
pub size: Option<LogicalSize<f32>>,
|
||||
pub position: Option<LogicalPosition<f32>>,
|
||||
pub last_click: Option<Instant>,
|
||||
pub malicious_origins: HashSet<String>,
|
||||
}
|
||||
|
||||
const AD_LINK: &str = "https://modrinth.com/wrapper/app-ads-cookie";
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::<R>::new("ads")
|
||||
.setup(|app, _api| {
|
||||
@@ -16,6 +24,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
shown: true,
|
||||
size: None,
|
||||
position: None,
|
||||
last_click: None,
|
||||
malicious_origins: HashSet::new(),
|
||||
}));
|
||||
|
||||
// We refresh the ads window every 5 minutes for performance
|
||||
@@ -24,11 +34,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
loop {
|
||||
if let Some(webview) = app.webviews().get_mut("ads-window")
|
||||
{
|
||||
let _ = webview.navigate(
|
||||
"https://modrinth.com/wrapper/app-ads-cookie"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
let _ = webview.navigate(AD_LINK.parse().unwrap());
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(60 * 5))
|
||||
@@ -43,6 +49,9 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
hide_ads_window,
|
||||
scroll_ads_window,
|
||||
show_ads_window,
|
||||
record_ads_click,
|
||||
open_link,
|
||||
get_ads_personalization,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -79,7 +88,7 @@ pub async fn init_ads_window<R: Runtime>(
|
||||
tauri::webview::WebviewBuilder::new(
|
||||
"ads-window",
|
||||
WebviewUrl::External(
|
||||
"https://modrinth.com/wrapper/app-ads-cookie".parse().unwrap(),
|
||||
AD_LINK.parse().unwrap(),
|
||||
),
|
||||
)
|
||||
.initialization_script(LINK_SCRIPT)
|
||||
@@ -159,3 +168,49 @@ pub async fn scroll_ads_window<R: Runtime>(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn record_ads_click<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> crate::api::Result<()> {
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
|
||||
let mut state = state.write().await;
|
||||
state.last_click = Some(Instant::now());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_link<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
path: String,
|
||||
origin: String,
|
||||
) -> crate::api::Result<()> {
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
let mut state = state.write().await;
|
||||
|
||||
if url::Url::parse(&path).is_ok()
|
||||
&& !state.malicious_origins.contains(&origin)
|
||||
{
|
||||
if let Some(last_click) = state.last_click {
|
||||
if last_click.elapsed() < Duration::from_millis(100) {
|
||||
let _ = app.shell().open(&path, None);
|
||||
state.last_click = None;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("Malicious click: {path} origin {origin}");
|
||||
state.malicious_origins.insert(origin);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_ads_personalization() -> crate::api::Result<bool> {
|
||||
let res = settings::get().await?;
|
||||
Ok(res.personalized_ads)
|
||||
}
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "http://timestamp.digicert.com",
|
||||
"wix": {
|
||||
"template": "./msi/main.wxs"
|
||||
},
|
||||
"nsis": {
|
||||
"installMode": "perMachine",
|
||||
"installerHooks": "./nsis/hooks.nsi"
|
||||
@@ -52,7 +49,8 @@
|
||||
]
|
||||
},
|
||||
"productName": "Modrinth App",
|
||||
"version": "0.8.7",
|
||||
"version": "0.8.9",
|
||||
"mainBinaryName": "Modrinth App",
|
||||
"identifier": "ModrinthApp",
|
||||
"plugins": {
|
||||
"deep-link": {
|
||||
|
||||
3
apps/app/tauri.linux.conf.json
Normal file
3
apps/app/tauri.linux.conf.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"mainBinaryName": "ModrinthApp"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="ad-parent relative mb-3 flex w-full justify-center rounded-2xl bg-bg-raised">
|
||||
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
|
||||
<p class="m-0 text-2xl font-bold text-contrast">90% of ad revenue goes to creators</p>
|
||||
<p class="m-0 text-2xl font-bold text-contrast">75% of ad revenue goes to creators</p>
|
||||
<nuxt-link to="/plus" class="mt-auto items-center gap-1 text-purple hover:underline">
|
||||
<span>
|
||||
Support creators and Modrinth ad-free with
|
||||
@@ -23,9 +23,19 @@ import { ChevronRightIcon } from "@modrinth/assets";
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
// Clean.io
|
||||
src: "https://cadmus.script.ac/d14pdm1b7fi5kh/script.js",
|
||||
},
|
||||
{
|
||||
// Aditude
|
||||
src: "https://dn0qt3r0xannq.cloudfront.net/modrinth-7JfmkEIXEp/modrinth-longform/prebid-load.js",
|
||||
async: true,
|
||||
},
|
||||
{
|
||||
// Optima
|
||||
src: "https://bservr.com/o.js?uid=8118d1fdb2e0d6f32180bd27",
|
||||
async: true,
|
||||
},
|
||||
{
|
||||
src: "/inmobi.js",
|
||||
async: true,
|
||||
@@ -47,6 +57,9 @@ onMounted(() => {
|
||||
{
|
||||
divId: "modrinth-rail-1",
|
||||
baseDivId: "pb-slot-square-2",
|
||||
targeting: {
|
||||
location: "web",
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -57,7 +57,7 @@ export function createDisplayNames(
|
||||
|
||||
try {
|
||||
return dict.of(lookup);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
console.warn(
|
||||
`Failed to get display name for ${lookup} using dictionary for ${
|
||||
this.resolvedOptions().locale
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import hljs from "highlight.js/lib/core";
|
||||
// Scripting
|
||||
import javascript from "highlight.js/lib/languages/javascript";
|
||||
import python from "highlight.js/lib/languages/python";
|
||||
import lua from "highlight.js/lib/languages/lua";
|
||||
import python from "highlight.js/lib/languages/python";
|
||||
// Coding
|
||||
import groovy from "highlight.js/lib/languages/groovy";
|
||||
import java from "highlight.js/lib/languages/java";
|
||||
import kotlin from "highlight.js/lib/languages/kotlin";
|
||||
import scala from "highlight.js/lib/languages/scala";
|
||||
import groovy from "highlight.js/lib/languages/groovy";
|
||||
// Configs
|
||||
import { configuredXss, md } from "@modrinth/utils";
|
||||
import gradle from "highlight.js/lib/languages/gradle";
|
||||
import json from "highlight.js/lib/languages/json";
|
||||
import ini from "highlight.js/lib/languages/ini";
|
||||
import yaml from "highlight.js/lib/languages/yaml";
|
||||
import xml from "highlight.js/lib/languages/xml";
|
||||
import json from "highlight.js/lib/languages/json";
|
||||
import properties from "highlight.js/lib/languages/properties";
|
||||
import { md, configuredXss } from "@modrinth/utils";
|
||||
import xml from "highlight.js/lib/languages/xml";
|
||||
import yaml from "highlight.js/lib/languages/yaml";
|
||||
|
||||
/* REGISTRATION */
|
||||
// Scripting
|
||||
@@ -54,7 +54,7 @@ export const renderHighlightedString = (string) =>
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return hljs.highlight(str, { language: lang }).value;
|
||||
} catch (__) {
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +231,7 @@
|
||||
</template>
|
||||
<template #revenue> <CurrencyIcon aria-hidden="true" /> Revenue </template>
|
||||
<template #analytics> <ChartIcon aria-hidden="true" /> Analytics </template>
|
||||
<template #moderation> <ModerationIcon aria-hidden="true" /> Moderation </template>
|
||||
<template #sign-out> <LogOutIcon aria-hidden="true" /> Sign out </template>
|
||||
</OverflowMenu>
|
||||
<ButtonStyled v-else color="brand">
|
||||
@@ -778,6 +779,7 @@ const userMenuOptions = computed(() => {
|
||||
link: "/settings",
|
||||
},
|
||||
];
|
||||
|
||||
// TODO: Only show if user has projects
|
||||
options = [
|
||||
...options,
|
||||
@@ -801,6 +803,24 @@ const userMenuOptions = computed(() => {
|
||||
link: "/dashboard/analytics",
|
||||
},
|
||||
];
|
||||
|
||||
if (
|
||||
(auth.value && auth.value.user && auth.value.user.role === "moderator") ||
|
||||
auth.value.user.role === "admin"
|
||||
) {
|
||||
options = [
|
||||
...options,
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
id: "moderation",
|
||||
color: "orange",
|
||||
link: "/moderation/review",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
options = [
|
||||
...options,
|
||||
{
|
||||
|
||||
@@ -1398,7 +1398,7 @@ try {
|
||||
|
||||
versions = shallowRef(toRaw(versions));
|
||||
featuredVersions = shallowRef(toRaw(featuredVersions));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
throw createError({
|
||||
fatal: true,
|
||||
statusCode: 404,
|
||||
|
||||
@@ -95,7 +95,7 @@ const props = defineProps<{
|
||||
members: User[];
|
||||
currentMember: User;
|
||||
dependencies: Dependency[];
|
||||
resetProject: Function;
|
||||
resetProject: (opts?: { dedupe?: "cancel" | "defer" }) => Promise<void>;
|
||||
}>();
|
||||
|
||||
const version = computed(() => {
|
||||
|
||||
@@ -27,6 +27,7 @@ const windowsLink = ref(null);
|
||||
const linuxLinks = {
|
||||
appImage: null,
|
||||
deb: null,
|
||||
rpm: null,
|
||||
thirdParty: "https://support.modrinth.com/en/articles/9298760",
|
||||
};
|
||||
const macLinks = {
|
||||
@@ -57,6 +58,7 @@ macLinks.intel = launcherUpdates.value.platforms["darwin-x86_64"].install_urls[0
|
||||
windowsLink.value = launcherUpdates.value.platforms["windows-x86_64"].install_urls[0];
|
||||
linuxLinks.appImage = launcherUpdates.value.platforms["linux-x86_64"].install_urls[1];
|
||||
linuxLinks.deb = launcherUpdates.value.platforms["linux-x86_64"].install_urls[0];
|
||||
linuxLinks.rpm = launcherUpdates.value.platforms["linux-x86_64"].install_urls[2];
|
||||
|
||||
onMounted(() => {
|
||||
os.value = navigator?.platform.toString();
|
||||
@@ -879,7 +881,11 @@ useSeoMeta({
|
||||
</a>
|
||||
<a :href="linuxLinks.deb" download="">
|
||||
<DownloadIcon />
|
||||
<span> Download the Deb </span>
|
||||
<span> Download the DEB </span>
|
||||
</a>
|
||||
<a :href="linuxLinks.rpm" download="">
|
||||
<DownloadIcon />
|
||||
<span> Download the RPM </span>
|
||||
</a>
|
||||
<a :href="linuxLinks.thirdParty" download="">
|
||||
<LinkIcon />
|
||||
|
||||
@@ -195,7 +195,7 @@ const onAuthorize = async () => {
|
||||
}
|
||||
|
||||
throw new Error(formatMessage(messages.noRedirectUrlError));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
data.$notify({
|
||||
group: "main",
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
@@ -222,7 +222,7 @@ const onReject = async () => {
|
||||
}
|
||||
|
||||
throw new Error(formatMessage(messages.noRedirectUrlError));
|
||||
} catch (error) {
|
||||
} catch {
|
||||
data.$notify({
|
||||
group: "main",
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
|
||||
@@ -145,7 +145,7 @@ if (route.query.flow) {
|
||||
await useAuth(auth.value.token);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
success.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,7 +612,7 @@ const visibility = ref(collection.value.status);
|
||||
const removeProjects = ref([]);
|
||||
|
||||
async function unfollowProject(project) {
|
||||
await userUnfollowProject(project);
|
||||
await userFollowProject(project);
|
||||
projects.value = projects.value.filter((x) => x.id !== project.id);
|
||||
}
|
||||
|
||||
|
||||
@@ -88,20 +88,6 @@
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div class="grid-display__item">
|
||||
<div class="label">Current balance</div>
|
||||
<div class="value">
|
||||
{{ $formatMoney(auth.user.payout_data.balance, true) }}
|
||||
</div>
|
||||
<NuxtLink
|
||||
v-if="auth.user.payout_data.balance > 0"
|
||||
class="goto-link"
|
||||
to="/dashboard/revenue"
|
||||
>
|
||||
Withdraw earnings
|
||||
<ChevronRightIcon class="featured-header-chevron" aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -2,21 +2,24 @@
|
||||
<div>
|
||||
<section class="universal-card">
|
||||
<h2 class="text-2xl">Revenue</h2>
|
||||
<div v-if="auth.user.payout_data.balance >= minWithdraw">
|
||||
<div v-if="userBalance.available >= minWithdraw">
|
||||
<p>
|
||||
You have
|
||||
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong>
|
||||
available to withdraw.
|
||||
<strong>{{ $formatMoney(userBalance.available) }}</strong>
|
||||
available to withdraw. <strong>{{ $formatMoney(userBalance.pending) }}</strong> of your
|
||||
balance is <nuxt-link class="text-link" to="/legal/cmp-info#pending">pending</nuxt-link>.
|
||||
</p>
|
||||
</div>
|
||||
<p v-else>
|
||||
You have made
|
||||
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong
|
||||
<strong>{{ $formatMoney(userBalance.available) }}</strong
|
||||
>, which is under the minimum of ${{ minWithdraw }} to withdraw.
|
||||
<strong>{{ $formatMoney(userBalance.pending) }}</strong> of your balance is
|
||||
<nuxt-link class="text-link" to="/legal/cmp-info#pending">pending</nuxt-link>.
|
||||
</p>
|
||||
<div class="input-group mt-4">
|
||||
<nuxt-link
|
||||
v-if="auth.user.payout_data.balance >= minWithdraw"
|
||||
v-if="userBalance.available >= minWithdraw"
|
||||
class="iconified-button brand-button"
|
||||
to="/dashboard/revenue/withdraw"
|
||||
>
|
||||
@@ -81,6 +84,10 @@ import { TransferIcon, HistoryIcon, PayPalIcon, SaveIcon, XIcon } from "@modrint
|
||||
const auth = await useAuth();
|
||||
const minWithdraw = ref(0.01);
|
||||
|
||||
const { data: userBalance } = await useAsyncData(`payout/balance`, () =>
|
||||
useBaseFetch(`payout/balance`, { apiVersion: 3 }),
|
||||
);
|
||||
|
||||
async function updateVenmo() {
|
||||
startLoading();
|
||||
try {
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<p>
|
||||
You are initiating a transfer of your revenue from Modrinth's Creator Monetization Program.
|
||||
How much of your
|
||||
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong> balance would you like to
|
||||
<strong>{{ $formatMoney(userBalance.available) }}</strong> balance would you like to transfer
|
||||
transfer to {{ selectedMethod.name }}?
|
||||
</p>
|
||||
<div class="confirmation-input">
|
||||
@@ -212,10 +212,13 @@ const country = ref(
|
||||
countries.value.find((x) => x.id === (auth.value.user.payout_data.paypal_region ?? "US")),
|
||||
);
|
||||
|
||||
const { data: payoutMethods, refresh: refreshPayoutMethods } = await useAsyncData(
|
||||
`payout/methods?country=${country.value.id}`,
|
||||
() => useBaseFetch(`payout/methods?country=${country.value.id}`, { apiVersion: 3 }),
|
||||
);
|
||||
const [{ data: userBalance }, { data: payoutMethods, refresh: refreshPayoutMethods }] =
|
||||
await Promise.all([
|
||||
useAsyncData(`payout/balance`, () => useBaseFetch(`payout/balance`, { apiVersion: 3 })),
|
||||
useAsyncData(`payout/methods?country=${country.value.id}`, () =>
|
||||
useBaseFetch(`payout/methods?country=${country.value.id}`, { apiVersion: 3 }),
|
||||
),
|
||||
]);
|
||||
|
||||
const selectedMethodId = ref(payoutMethods.value[0].id);
|
||||
const selectedMethod = computed(() =>
|
||||
@@ -295,10 +298,10 @@ const knownErrors = computed(() => {
|
||||
if (!parsedAmount.value && amount.value.length > 0) {
|
||||
errors.push(`${amount.value} is not a valid amount`);
|
||||
} else if (
|
||||
parsedAmount.value > auth.value.user.payout_data.balance ||
|
||||
parsedAmount.value > userBalance.value.available ||
|
||||
parsedAmount.value > maxWithdrawAmount.value
|
||||
) {
|
||||
const maxAmount = Math.min(auth.value.user.payout_data.balance, maxWithdrawAmount.value);
|
||||
const maxAmount = Math.min(userBalance.value.available, maxWithdrawAmount.value);
|
||||
errors.push(`The amount must be no more than ${data.$formatMoney(maxAmount)}`);
|
||||
} else if (parsedAmount.value <= fees.value || parsedAmount.value < minWithdrawAmount.value) {
|
||||
const minAmount = Math.max(fees.value + 0.01, minWithdrawAmount.value);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="markdown-body">
|
||||
<h1>Rewards Program Information</h1>
|
||||
<p><em>Last modified: May 13, 2024</em></p>
|
||||
<p><em>Last modified: Sep 12, 2024</em></p>
|
||||
<p>
|
||||
This page was created for transparency for how the rewards program works on Modrinth. Feel
|
||||
free to join our Discord or email
|
||||
@@ -15,10 +15,11 @@
|
||||
<h2>Rewards Distribution</h2>
|
||||
<p>
|
||||
We collect ad revenue on our website and app through our ad network
|
||||
<a href="https://adrinth.com">Adrinth</a>. We then distribute this ad revenue to creators.
|
||||
<a href="https://adrinth.com">Adrinth</a>, which is powered by
|
||||
<a href="https://aditude.io">Aditude</a>. We then distribute this ad revenue to creators.
|
||||
</p>
|
||||
<p>
|
||||
The advertising revenue of the entire website and app is split 90% to creators and 10% to
|
||||
The advertising revenue of the entire website and app is split 75% to creators and 25% to
|
||||
Modrinth.
|
||||
</p>
|
||||
<p>
|
||||
@@ -42,10 +43,10 @@
|
||||
</ul>
|
||||
<p>In this scenario, the earnings for each creator and Modrinth would be as follows:</p>
|
||||
<ul>
|
||||
<li>Modrinth: $10 (10% of $100, the site's earnings for the day)</li>
|
||||
<li>User A: $58.69 ($90 * (10 + 30 + 100 + 10)/230)</li>
|
||||
<li>User B: $12.52 (0.4 * $90 * (50 + 20 + 10 + 0)/230)</li>
|
||||
<li>User C: $18.78 (0.6 * $90 * (50 + 20 + 10 + 0)/230)</li>
|
||||
<li>Modrinth: $25 (25% of $100, the site's earnings for the day)</li>
|
||||
<li>User A: $48.91 ($75 * (10 + 30 + 100 + 10)/230)</li>
|
||||
<li>User B: $10.43 (0.4 * $75 * (50 + 20 + 10 + 0)/230)</li>
|
||||
<li>User C: $15.65 (0.6 * $75 * (50 + 20 + 10 + 0)/230)</li>
|
||||
<li>Note: 230 is the sum of all page views and in-app downloads from above</li>
|
||||
</ul>
|
||||
<p>
|
||||
@@ -72,19 +73,62 @@
|
||||
</p>
|
||||
<h3>What methods can I use withdraw money from my account? Are there any fees?</h3>
|
||||
<p>
|
||||
Right now, you can use PayPal or Venmo to withdraw money from your Modrinth account. We are
|
||||
working on more methods to withdraw money from your account. There are fees to withdraw money
|
||||
from your Modrinth account—see the revenue page in your dashboard for more information.
|
||||
Right now, you can use PayPal or Venmo to withdraw money from your Modrinth account. Gift card
|
||||
withdrawal is also available. We are working on more methods to withdraw money from your
|
||||
account. There are fees to withdraw money from your Modrinth account—see the revenue page in
|
||||
your dashboard for more information.
|
||||
</p>
|
||||
<h3>Modrinth used to give 100% of project page revenue to creators. What changed?</h3>
|
||||
<h3 id="pending">What does "pending" revenue mean in my dashboard?</h3>
|
||||
<p>
|
||||
While this is true, our new system (as of 08/05/23) gives more of the site's revenue to
|
||||
creators, so creators will earn more. In the old system, we would earn revenue through
|
||||
advertisements in search and user profile pages. This amounted on average each month to about
|
||||
15-20% of the site's total advertising revenue (so a 80-85% split to creators). The new system
|
||||
gives creators more revenue and a more favorable split towards creators (90%).
|
||||
Modrinth receives ad revenue from our ad providers on a NET 60 day basis. Due to this, not all
|
||||
revenue is immediately available to withdraw. We pay creators as soon as we receive the money
|
||||
from our ad providers, which is 60 days after the last day of each month. This table outlines
|
||||
some example dates of how NET 60 payments are made:
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Payment available date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>January 1st</td>
|
||||
<td>March 31st</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>January 15th</td>
|
||||
<td>March 31st</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>March 3rd</td>
|
||||
<td>May 30th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>June 30th</td>
|
||||
<td>August 29th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>July 14th</td>
|
||||
<td>September 29th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>October 12th</td>
|
||||
<td>December 30th</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>How do I know Modrinth is being transparent about revenue?</h3>
|
||||
<p>
|
||||
We aim to be as transparent as possible with creator revenue. All of our code is open source,
|
||||
including our
|
||||
<a href="https://github.com/modrinth/labrinth/blob/master/src/queue/payouts.rs#L561">
|
||||
revenue distribution system </a
|
||||
>. We also have an
|
||||
<a href="https://api.modrinth.com/v3/payout/platform_revenue">API route</a> that allows users
|
||||
to query exact daily revenue for the site.
|
||||
</p>
|
||||
<h3></h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -371,6 +371,22 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="data-export" class="universal-card">
|
||||
<h2>Data export</h2>
|
||||
<p>
|
||||
Request a copy of all your personal data you have uploaded to Modrinth. This may take
|
||||
several minutes to complete.
|
||||
</p>
|
||||
<a v-if="generated" class="iconified-button" :href="generated" download="export.json">
|
||||
<DownloadIcon />
|
||||
Download export
|
||||
</a>
|
||||
<button v-else class="iconified-button" :disabled="generatingExport" @click="exportData">
|
||||
<template v-if="generatingExport"> <UpdatedIcon /> Generating export... </template>
|
||||
<template v-else> <UpdatedIcon /> Generate export </template>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section id="delete-account" class="universal-card">
|
||||
<h2>Delete account</h2>
|
||||
<p>
|
||||
@@ -391,16 +407,18 @@
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
EditIcon,
|
||||
SaveIcon,
|
||||
TrashIcon,
|
||||
PlusIcon,
|
||||
SettingsIcon,
|
||||
XIcon,
|
||||
LeftArrowIcon,
|
||||
RightArrowIcon,
|
||||
CheckIcon,
|
||||
EditIcon,
|
||||
ExternalIcon,
|
||||
LeftArrowIcon,
|
||||
PlusIcon,
|
||||
RightArrowIcon,
|
||||
SaveIcon,
|
||||
SettingsIcon,
|
||||
TrashIcon,
|
||||
UpdatedIcon,
|
||||
XIcon,
|
||||
DownloadIcon,
|
||||
} from "@modrinth/assets";
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
import GitHubIcon from "assets/icons/auth/sso-github.svg";
|
||||
@@ -538,7 +556,7 @@ async function verifyTwoFactorCode() {
|
||||
backupCodes.value = res.backup_codes;
|
||||
twoFactorStep.value = 2;
|
||||
await useAuth(auth.value.token);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
twoFactorIncorrect.value = true;
|
||||
}
|
||||
stopLoading();
|
||||
@@ -555,7 +573,7 @@ async function removeTwoFactor() {
|
||||
});
|
||||
manageTwoFactorModal.value.hide();
|
||||
await useAuth(auth.value.token);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
twoFactorIncorrect.value = true;
|
||||
}
|
||||
stopLoading();
|
||||
@@ -614,6 +632,34 @@ async function deleteAccount() {
|
||||
|
||||
stopLoading();
|
||||
}
|
||||
|
||||
const generatingExport = ref(false);
|
||||
const generated = ref();
|
||||
async function exportData() {
|
||||
startLoading();
|
||||
generatingExport.value = true;
|
||||
try {
|
||||
const res = await useBaseFetch("gdpr/export", {
|
||||
method: "POST",
|
||||
internal: true,
|
||||
});
|
||||
|
||||
const jsonString = JSON.stringify(res, null, 2);
|
||||
|
||||
const blob = new Blob([jsonString], { type: "application/json" });
|
||||
generated.value = URL.createObjectURL(blob);
|
||||
} catch (err) {
|
||||
data.$notify({
|
||||
group: "main",
|
||||
title: "An error occurred",
|
||||
text: err.data.description,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
|
||||
generatingExport.value = false;
|
||||
stopLoading();
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
canvas {
|
||||
|
||||
@@ -301,7 +301,7 @@ const canSubmit = computed(() => {
|
||||
const url = new URL(uri);
|
||||
return !!url;
|
||||
});
|
||||
} catch (err) {
|
||||
} catch {
|
||||
allValid = false;
|
||||
}
|
||||
return filledIn && (oneValid || allValid);
|
||||
|
||||
@@ -17,33 +17,24 @@
|
||||
<span class="font-bold text-primary">
|
||||
<template v-if="charge.product.metadata.type === 'midas'"> Modrinth Plus </template>
|
||||
<template v-else> Unknown product </template>
|
||||
<template v-if="charge.metadata.modrinth_subscription_interval">
|
||||
{{ charge.metadata.modrinth_subscription_interval }}
|
||||
<template v-if="charge.subscription_interval">
|
||||
{{ charge.subscription_interval }}
|
||||
</template>
|
||||
</span>
|
||||
⋅
|
||||
<span>{{ formatPrice(charge.amount, charge.currency) }}</span>
|
||||
<span>{{ formatPrice(charge.amount, charge.currency_code) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Badge :color="charge.status === 'succeeded' ? 'green' : 'red'" :type="charge.status" />
|
||||
⋅
|
||||
{{ $dayjs.unix(charge.created).format("YYYY-MM-DD") }}
|
||||
{{ $dayjs(charge.due).format("YYYY-MM-DD") }}
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
v-if="charge.receipt_url"
|
||||
class="iconified-button raised-button"
|
||||
:href="charge.receipt_url"
|
||||
>
|
||||
<ReceiptTextIcon />
|
||||
View receipt
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ReceiptTextIcon } from "@modrinth/assets";
|
||||
import { Breadcrumbs, Badge } from "@modrinth/ui";
|
||||
import { products } from "~/generated/state.json";
|
||||
|
||||
@@ -58,15 +49,17 @@ const { data: charges } = await useAsyncData(
|
||||
() => useBaseFetch("billing/payments", { internal: true }),
|
||||
{
|
||||
transform: (charges) => {
|
||||
return charges.map((charge) => {
|
||||
const product = products.find((product) =>
|
||||
product.prices.some((price) => price.id === charge.metadata.modrinth_price_id),
|
||||
);
|
||||
return charges
|
||||
.filter((charge) => charge.status !== "open" && charge.status !== "cancelled")
|
||||
.map((charge) => {
|
||||
const product = products.find((product) =>
|
||||
product.prices.some((price) => price.id === charge.price_id),
|
||||
);
|
||||
|
||||
charge.product = product;
|
||||
charge.product = product;
|
||||
|
||||
return charge;
|
||||
});
|
||||
return charge;
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -8,22 +8,20 @@
|
||||
:title="formatMessage(cancelModalMessages.title)"
|
||||
:description="formatMessage(cancelModalMessages.description)"
|
||||
:proceed-label="formatMessage(cancelModalMessages.action)"
|
||||
@proceed="cancelSubscription(cancelSubscriptionId)"
|
||||
@proceed="cancelSubscription(cancelSubscriptionId, true)"
|
||||
/>
|
||||
<div class="flex flex-wrap justify-between gap-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<template v-if="midasSubscription">
|
||||
<span v-if="midasSubscription.status === 'active'">
|
||||
You're currently subscribed to:
|
||||
</span>
|
||||
<span v-else-if="midasSubscription.status === 'payment-processing'" class="text-orange">
|
||||
<template v-if="midasCharge">
|
||||
<span v-if="midasCharge.status === 'open'"> You're currently subscribed to: </span>
|
||||
<span v-else-if="midasCharge.status === 'processing'" class="text-orange">
|
||||
Your payment is being processed. Perks will activate once payment is complete.
|
||||
</span>
|
||||
<span v-else-if="midasSubscription.status === 'cancelled'">
|
||||
<span v-else-if="midasCharge.status === 'cancelled'">
|
||||
You've cancelled your subscription. <br />
|
||||
You will retain your perks until the end of the current billing cycle.
|
||||
</span>
|
||||
<span v-else-if="midasSubscription.status === 'payment-failed'" class="text-red">
|
||||
<span v-else-if="midasCharge.status === 'failed'" class="text-red">
|
||||
Your subscription payment failed. Please update your payment method.
|
||||
</span>
|
||||
</template>
|
||||
@@ -49,34 +47,31 @@
|
||||
<div class="flex w-full flex-wrap justify-between gap-4 xl:w-auto xl:flex-col">
|
||||
<div class="flex flex-col gap-1 xl:ml-auto xl:text-right">
|
||||
<span class="text-2xl font-bold text-dark">
|
||||
<template v-if="midasSubscription">
|
||||
<template v-if="midasCharge">
|
||||
{{
|
||||
formatPrice(
|
||||
vintl.locale,
|
||||
midasSubscriptionPrice.prices.intervals[midasSubscription.interval],
|
||||
midasSubscriptionPrice.prices.intervals[midasCharge.subscription_interval],
|
||||
midasSubscriptionPrice.currency_code,
|
||||
)
|
||||
}}
|
||||
/
|
||||
{{ midasSubscription.interval }}
|
||||
{{ midasCharge.subscription_interval }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formatPrice(vintl.locale, price.prices.intervals.monthly, price.currency_code) }}
|
||||
/ month
|
||||
</template>
|
||||
</span>
|
||||
<template v-if="midasSubscription">
|
||||
<template v-if="midasCharge">
|
||||
<span class="text-sm text-secondary">
|
||||
Since {{ $dayjs(midasSubscription.created).format("MMMM D, YYYY") }}
|
||||
</span>
|
||||
<span v-if="midasSubscription.status === 'active'" class="text-sm text-secondary">
|
||||
Renews {{ $dayjs(midasSubscription.expires).format("MMMM D, YYYY") }}
|
||||
<span v-if="midasCharge.status === 'open'" class="text-sm text-secondary">
|
||||
Renews {{ $dayjs(midasCharge.due).format("MMMM D, YYYY") }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="midasSubscription.status === 'cancelled'"
|
||||
class="text-sm text-secondary"
|
||||
>
|
||||
Expires {{ $dayjs(midasSubscription.expires).format("MMMM D, YYYY") }}
|
||||
<span v-else-if="midasCharge.status === 'cancelled'" class="text-sm text-secondary">
|
||||
Expires {{ $dayjs(midasCharge.due).format("MMMM D, YYYY") }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -90,11 +85,11 @@
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="midasSubscription && midasSubscription.status === 'payment-failed'"
|
||||
v-if="midasCharge && midasCharge.status === 'failed'"
|
||||
class="ml-auto flex flex-row-reverse items-center gap-2"
|
||||
>
|
||||
<button
|
||||
v-if="midasSubscription && midasSubscription.status === 'payment-failed'"
|
||||
v-if="midasCharge && midasCharge.status === 'failed'"
|
||||
class="iconified-button raised-button"
|
||||
@click="
|
||||
() => {
|
||||
@@ -123,7 +118,7 @@
|
||||
</OverflowMenu>
|
||||
</div>
|
||||
<button
|
||||
v-else-if="midasSubscription && midasSubscription.status !== 'cancelled'"
|
||||
v-else-if="midasCharge && midasCharge.status !== 'cancelled'"
|
||||
class="iconified-button raised-button !ml-auto"
|
||||
@click="
|
||||
() => {
|
||||
@@ -134,6 +129,13 @@
|
||||
>
|
||||
<XIcon /> Cancel
|
||||
</button>
|
||||
<button
|
||||
v-else-if="midasCharge && midasCharge.status === 'cancelled'"
|
||||
class="btn btn-purple btn-large ml-auto"
|
||||
@click="cancelSubscription(midasSubscription.id, false)"
|
||||
>
|
||||
<RightArrowIcon /> Resubscribe
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-purple btn-large ml-auto"
|
||||
@@ -474,12 +476,14 @@ function loadStripe() {
|
||||
|
||||
const [
|
||||
{ data: paymentMethods, refresh: refreshPaymentMethods },
|
||||
{ data: charges, refresh: refreshCharges },
|
||||
{ data: customer, refresh: refreshCustomer },
|
||||
{ data: subscriptions, refresh: refreshSubscriptions },
|
||||
] = await Promise.all([
|
||||
useAsyncData("billing/payment_methods", () =>
|
||||
useBaseFetch("billing/payment_methods", { internal: true }),
|
||||
),
|
||||
useAsyncData("billing/payments", () => useBaseFetch("billing/payments", { internal: true })),
|
||||
useAsyncData("billing/customer", () => useBaseFetch("billing/customer", { internal: true })),
|
||||
useAsyncData("billing/subscriptions", () =>
|
||||
useBaseFetch("billing/subscriptions", { internal: true }),
|
||||
@@ -487,18 +491,30 @@ const [
|
||||
]);
|
||||
|
||||
async function refresh() {
|
||||
await Promise.all([refreshPaymentMethods(), refreshCustomer(), refreshSubscriptions()]);
|
||||
await Promise.all([
|
||||
refreshPaymentMethods(),
|
||||
refreshCharges(),
|
||||
refreshCustomer(),
|
||||
refreshSubscriptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
const midasProduct = ref(products.find((x) => x.metadata.type === "midas"));
|
||||
const midasSubscription = computed(() =>
|
||||
subscriptions.value.find((x) => midasProduct.value.prices.find((y) => y.id === x.price_id)),
|
||||
subscriptions.value.find(
|
||||
(x) => x.status === "provisioned" && midasProduct.value.prices.find((y) => y.id === x.price_id),
|
||||
),
|
||||
);
|
||||
const midasSubscriptionPrice = computed(() =>
|
||||
midasSubscription.value
|
||||
? midasProduct.value.prices.find((x) => x.id === midasSubscription.value.price_id)
|
||||
: null,
|
||||
);
|
||||
const midasCharge = computed(() =>
|
||||
midasSubscription.value
|
||||
? charges.value.find((x) => x.subscription_id === midasSubscription.value.id)
|
||||
: null,
|
||||
);
|
||||
|
||||
const purchaseModal = ref();
|
||||
const country = useUserCountry();
|
||||
@@ -524,10 +540,18 @@ if (route.query.priceId && route.query.plan && route.query.redirect_status) {
|
||||
price_id: route.query.priceId,
|
||||
interval: route.query.plan,
|
||||
created: Date.now(),
|
||||
expires: route.query.plan === "yearly" ? Date.now() + 31536000000 : Date.now() + 2629746000,
|
||||
status,
|
||||
});
|
||||
|
||||
charges.value.push({
|
||||
id: "temp",
|
||||
price_id: route.query.priceId,
|
||||
subscription_id: "temp",
|
||||
status: "open",
|
||||
due: Date.now() + (route.query.plan === "yearly" ? 31536000000 : 2629746000),
|
||||
subscription_interval: route.query.plan,
|
||||
});
|
||||
|
||||
await router.replace({ query: {} });
|
||||
}
|
||||
|
||||
@@ -655,12 +679,15 @@ async function removePaymentMethod(index) {
|
||||
}
|
||||
|
||||
const cancelSubscriptionId = ref();
|
||||
async function cancelSubscription(id) {
|
||||
async function cancelSubscription(id, cancelled) {
|
||||
startLoading();
|
||||
try {
|
||||
await useBaseFetch(`billing/subscription/${id}`, {
|
||||
internal: true,
|
||||
method: "DELETE",
|
||||
method: "PATCH",
|
||||
body: {
|
||||
cancelled,
|
||||
},
|
||||
});
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
|
||||
@@ -226,7 +226,7 @@ async function changeLocale(value: string) {
|
||||
try {
|
||||
await vintl.changeLocale(value);
|
||||
$failedLocale.value = undefined;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
$failedLocale.value = value;
|
||||
} finally {
|
||||
$changingTo.value = undefined;
|
||||
|
||||
@@ -474,3 +474,167 @@ krushmedia.com, AJxF6R537a9M6CaTvK, RESELLER
|
||||
consumable.com, 2001460, DIRECT, aefcd3d2f45b5070
|
||||
|
||||
google.com, pub-6729046591418183, RESELLER, f08c47fec0942fa0
|
||||
|
||||
pgamssp.com, 642db2eba4eb3f6b34033505, DIRECT
|
||||
freewheel.tv, 1489202, RESELLER
|
||||
freewheel.tv, 1488706, RESELLER
|
||||
pubmatic.com, 162623, RESELLER, 5d62403b186f2ace
|
||||
rubiconproject.com, 24852, RESELLER, 0bfd66d529a55807
|
||||
video.unrulymedia.com, 5921144960123684292, RESELLER
|
||||
minutemedia.com, 01hp4as4p012, RESELLER
|
||||
onetag.com, 87f80e5d9d55274, RESELLER
|
||||
sharethrough.com, FhiWXM0L, RESELLER, d53b998a7bd4ecd2
|
||||
amxrtb.com, 105199776, RESELLER
|
||||
33across.com, 0015a00003ALsDfAAL, DIRECT, bbea06d9c4d2853c
|
||||
risecodes.com, 661fc591c3a3ef0001984071, DIRECT
|
||||
|
||||
optimanetwork.com, 10581, DIRECT
|
||||
rubiconproject.com, 23404, DIRECT, 0bfd66d529a55807
|
||||
pubmatic.com,163370,RESELLER,5d62403b186f2ace
|
||||
criteo.com, B-060927, DIRECT, 9fac4a4a87c2a44f
|
||||
themediagrid.com, TR634Q, DIRECT, 35d5010d7789b49d
|
||||
google.com, pub-7094677798399606, RESELLER, f08c47fec0942fa0
|
||||
openx.com, 544021216, RESELLER, 6a698e2ec38604c6
|
||||
google.com, pub-2403018226404213, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-2730263451308801, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-5200956238394958, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-5761017298734489, RESELLER, f08c47fec0942fa0
|
||||
Pubmatic.com, 161094, DIRECT, 5d62403b186f2ace
|
||||
Pubmatic.com, 162736, DIRECT, 5d62403b186f2ace
|
||||
rubiconproject.com, 23978, RESELLER, 0bfd66d529a55807
|
||||
openx.com, 545708355, DIRECT, 6a698e2ec38604c6
|
||||
openx.com, 558218698, DIRECT, 6a698e2ec38604c6
|
||||
google.com, pub-3479162065560922, DIRECT, f08c47fec0942fa0
|
||||
google.com, pub-6631347089063164, DIRECT, f08c47fec0942fa0
|
||||
google.com, pub-1309378188566785, DIRECT, f08c47fec0942fa0
|
||||
google.com, pub-1305372857617183, DIRECT, f08c47fec0942fa0
|
||||
crads.in, CR-0029, DIRECT
|
||||
google.com, pub-6013343350303951, RESELLER, f08c47fec0942fa0
|
||||
rtbhouse.com, Jz6bvciZ65MalvqnKChX, DIRECT
|
||||
incrementx.com, 60039, DIRECT, 8728b7e97e589da4
|
||||
google.com, pub-3977122154505186, RESELLER, f08c47fec0942fa0
|
||||
pubmatic.com, 163277, RESELLER, 5d62403b186f2ace
|
||||
smartadserver.com, 4417-OB, RESELLER, 060d053dcf45cbf3
|
||||
lijit.com, 381276-eb, RESELLER, fafdf38b16bf6b2b
|
||||
rubiconproject.com, 22884, RESELLER, 0bfd66d529a55807
|
||||
onetag.com, 80faba581f6cfb8, RESELLER
|
||||
onetag.com, 80faba581f6cfb8-OB, RESELLER
|
||||
axonix.com, 59089, RESELLER, bc385f2b4a87b721
|
||||
outbrain.com,002d7f7ba0bd74452f2b155d0dfb5cd6c8,RESELLER
|
||||
opera.com,pub10952251675200,RESELLER,55a0c5fd61378de3
|
||||
pubmatic.com, 161652, RESELLER, 5d62403b186f2ace
|
||||
pubmatic.com, 164418, RESELLER, 5d62403b186f2ace
|
||||
152media.info,152M748,RESELLER
|
||||
appnexus.com, 3153, RESELLER, f5ab79cb980f11d1
|
||||
vidoomy.com, 61162, DIRECT
|
||||
video.unrulymedia.com, 1816262719, RESELLER,
|
||||
pubmatic.com, 156498, RESELLER, 5d62403b186f2ace
|
||||
freewheel.tv, 872257, RESELLER
|
||||
freewheel.tv, 894193, RESELLER
|
||||
openx.com, 540804929, RESELLER, 6a698e2ec38604c6
|
||||
google.com, pub-2831120411392012, RESELLER, f08c47fec0942fa0
|
||||
tremorhub.com, 4cywq-a04wk, RESELLER, 1a4e959a1b50034a
|
||||
rubiconproject.com, 24386, RESELLER, 0bfd66d529a55807
|
||||
pubmatic.com, 165144, RESELLER, 5d62403b186f2ace
|
||||
smartadserver.com, 4984, RESELLER, 060d053dcf45cbf3
|
||||
truvid.com, 2158, DIRECT
|
||||
smartadserver.com, 3356, RESELLER, 060d053dcf45cbf3
|
||||
google.com, pub-6322541192886560, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-8787923930478618, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-9507736279911916, RESELLER, f08c47fec0942fa0
|
||||
appnexus.com, 12700, RESELLER, f5ab79cb980f11d1
|
||||
rubiconproject.com, 17412, RESELLER, 0bfd66d529a55807
|
||||
conversantmedia.com, 100712, RESELLER, 03113cd04947736d
|
||||
vidcrunch.com, 63c6b945dee370f45c083ed4, DIRECT
|
||||
vdo.ai, 2213_3552, DIRECT
|
||||
atlas5.co, 2213_3552, DIRECT
|
||||
walletcircle.co, 2213, DIRECT
|
||||
google.com, pub-9417114411593463, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-5717092533913515, RESELLER, f08c47fec0942fa0
|
||||
aps.amazon.com, 24b39613-fd0f-4009-9189-976a7d9bfd3d, DIRECT
|
||||
gannett.com, 22655109307, RESELLER
|
||||
rubiconproject.com, 10968, RESELLER, 0bfd66d529a55807
|
||||
google.com, pub-4836542095728076, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-2930805104418204, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-9135355251665930, RESELLER, f08c47fec0942fa0
|
||||
freewheel.tv, 1139281, RESELLER
|
||||
aps.amazon.com, 00ed17ab-4189-4639-9d5e-15acd40affde, DIRECT
|
||||
smartadserver.com, 3686, RESELLER
|
||||
unibots.in, UBC-0165-1, DIRECT
|
||||
google.com, pub-1290995901905588, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-2603664881560000, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-3132893725603935, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-3191289882045155, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-3328898302928686, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-3769010358500643, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-5995202563537249, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-6151720204273327, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-6512936480753445, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-8241049497608997, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-8610050614645263, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-8699255262206653, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-9033099948928268, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-9467340974789471, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-9557089510405422, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-9685734445476814, RESELLER, f08c47fec0942fa0
|
||||
biddo.net, bdc-3969, DIRECT
|
||||
prodooh.com, pdh-3969, DIRECT
|
||||
invamia.com, ivm-3969, DIRECT
|
||||
vidverto.com, vdvt-3969, DIRECT
|
||||
yieldbird.com, 3969, DIRECT
|
||||
bidfuse.com, bf-3969, DIRECT
|
||||
arabella.ag, abl-3969, DIRECT
|
||||
adtech-digital.com, adt3969, DIRECT
|
||||
pixfuture.com, 9048, DIRECT
|
||||
openx.com, 540406323, DIRECT, 6a698e2ec38604c6
|
||||
pubmatic.com, 158127, RESELLER, 5d62403b186f2ace
|
||||
sonobi.com, 0b24fdfc82, DIRECT, d1a215d9eb5aee9e
|
||||
sharethrough.com, 62beb9db, DIRECT, d53b998a7bd4ecd2
|
||||
d-sail.com, ds0453, DIRECT
|
||||
google.com, pub-2205121062140812, RESELLER, f08c47fec0942fa0
|
||||
33across.com, 0010b00002VYZoUAAX, DIRECT, bbea06d9c4d2853c
|
||||
smartadserver.com, 3663, RESELLER
|
||||
smartadserver.com, 4071, RESELLER
|
||||
adtelligent.com, 500592, DIRECT
|
||||
sovrn.com, 331050, DIRECT, fafdf38b16bf6b2b
|
||||
rubiconproject.com, 23564, DIRECT, 0bfd66d529a55807
|
||||
rubiconproject.com, 23566, RESELLER, 0bfd66d529a55807
|
||||
Media.net, 8CUIUMTP7, DIRECT
|
||||
e-planning.net, 29459e615aaa9065, DIRECT, c1ba615865ed87b2
|
||||
themediagrid.com, QDC97I, DIRECT, 35d5010d7789b49d
|
||||
onetag.com, 79c9089f60042c0, DIRECT
|
||||
smilewanted.com, 3520, RESELLER
|
||||
smartadserver.com, 2491, RESELLER
|
||||
minutemedia.com,01hj1rkkswwe, RESELLER
|
||||
appnexus.com, 8381, RESELLER, f5ab79cb980f11d1
|
||||
aniview.com, 60c5dbc56627cd1ce66ff020, RESELLER, 78b21b97965ec3f8
|
||||
google.com, pub-6346866704322274, RESELLER, f08c47fec0942fa0
|
||||
google.com, pub-4586415728471297, RESELLER, f08c47fec0942fa0
|
||||
playstream.media, 911, RESELLER
|
||||
sharethrough.com, UXUWG46h, RESELLER, d53b998a7bd4ecd2
|
||||
sharethrough.com, zLsEa05k, RESELLER, d53b998a7bd4ecd2
|
||||
pubmatic.com, 156557, RESELLER, 5d62403b186f2ace
|
||||
vidgyor.com,20020, DIRECT
|
||||
google.com, pub-8863191426564274, RESELLER, f08c47fec0942fa0
|
||||
pubmatic.com, 162319, RESELLER, 5d62403b186f2ace
|
||||
lijit.com, 230102, DIRECT, fafdf38b16bf6b2b #SOVRN
|
||||
lijit.com, 230102-eb, DIRECT, fafdf38b16bf6b2b #SOVRN
|
||||
video.unrulymedia.com, 2444764291, RESELLER
|
||||
contextweb.com, 558511, RESELLER, 89ff185a4c4e857c
|
||||
krushmedia.com, AJxF6R572a9M6CaTvK, RESELLER
|
||||
inmobi.com, b01aa06531c543d8a5fb9982f60afb00, RESELLER, 83e75a7ae333ca9d
|
||||
motorik.io, 100463, RESELLER
|
||||
smaato.com, 1100056344, RESELLER, 07bcf65f187117b4
|
||||
smartadserver.com, 4926, RESELLER, 060d053dcf45cbf3
|
||||
onetag.com, 5847a7d7e75dee8, DIRECT
|
||||
video.unrulymedia.com, 351983115, RESELLER
|
||||
rubiconproject.com, 15268, RESELLER, 0bfd66d529a55807
|
||||
pubmatic.com, 159277, RESELLER
|
||||
appnexus.com, 6849, RESELLER
|
||||
xad.com, 963, RESELLER, 81cbf0a75a5e0e9a
|
||||
openx.com, 543878511, RESELLER, 6a698e2ec38604c6
|
||||
smaato.com, 1100047589, RESELLER, 07bcf65f187117b4
|
||||
inmobi.com, ba5fd3fb82c5412989b23c3eec71baf7, RESELLER, 83e75a7ae333ca9d
|
||||
|
||||
#Criteo
|
||||
themediagrid.com, 4R7XKQ, DIRECT, 35d5010d7789b49d
|
||||
|
||||
@@ -1,90 +1,132 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Modrinth App Ad</title>
|
||||
<script
|
||||
src="https://dn0qt3r0xannq.cloudfront.net/modrinth-7JfmkEIXEp/modrinth-longform/prebid-load.js"
|
||||
async
|
||||
></script>
|
||||
<link rel="preload" href="https://www.googletagservices.com/tag/js/gpt.js" as="script" />
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ads-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#plus-link {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#modrinth-rail-1 {
|
||||
border-radius: 1rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="ads-container">
|
||||
<a id="plus-link" href="https://modrinth.com/plus" target="_blank"></a>
|
||||
<div id="modrinth-rail-1" />
|
||||
</div>
|
||||
<script>
|
||||
window.tude = window.tude || { cmd: [] };
|
||||
tude.cmd.push(function () {
|
||||
tude.refreshAdsViaDivMappings([
|
||||
{
|
||||
divId: "modrinth-rail-1",
|
||||
baseDivId: "pb-slot-square-2",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
let lastUrl = null
|
||||
window.addEventListener(
|
||||
"message",
|
||||
(event) => {
|
||||
if (event.data.modrinthOpenUrl && window.__TAURI_INTERNALS__ && lastUrl !== event.data.modrinthOpenUrl) {
|
||||
lastUrl = event.data.modrinthOpenUrl
|
||||
window.__TAURI_INTERNALS__.invoke("plugin:shell|open", {
|
||||
path: event.data.modrinthOpenUrl,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
lastUrl = null
|
||||
}, 500)
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Modrinth App Ad</title>
|
||||
<script src="https://cadmus.script.ac/d14pdm1b7fi5kh/script.js"></script>
|
||||
<script
|
||||
src="https://dn0qt3r0xannq.cloudfront.net/modrinth-7JfmkEIXEp/modrinth-longform/prebid-load.js"
|
||||
async
|
||||
></script>
|
||||
<link rel="preload" href="https://www.googletagservices.com/tag/js/gpt.js" as="script" />
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
window.addEventListener("mousewheel", (event) => {
|
||||
if (window.__TAURI_INTERNALS__) {
|
||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|scroll_ads_window", {
|
||||
scroll: event.deltaY,
|
||||
.ads-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#plus-link {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#modrinth-rail-1 {
|
||||
border-radius: 1rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="ads-container">
|
||||
<div id="plus-link"></div>
|
||||
<div id="modrinth-rail-1"></div>
|
||||
</div>
|
||||
<script>
|
||||
function getCookie(name) {
|
||||
function escape(s) {
|
||||
return s.replace(/([.*+?\^$(){}|\[\]\/\\])/g, "\\$1");
|
||||
}
|
||||
|
||||
const match = document.cookie.match(RegExp("(?:^|;\\s*)" + escape(name) + "=([^;]*)"));
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function initAds(personalized) {
|
||||
window.tude = window.tude || { cmd: [] };
|
||||
|
||||
tude.cmd.push(function () {
|
||||
tude.refreshAdsViaDivMappings([
|
||||
{
|
||||
divId: "modrinth-rail-1",
|
||||
baseDivId: "pb-slot-square-2",
|
||||
targeting: {
|
||||
location: "app",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
tude.setPrivacySettings({
|
||||
personalizedAds: personalized ?? true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
if (window.__TAURI_INTERNALS__) {
|
||||
window.__TAURI_INTERNALS__
|
||||
.invoke("plugin:ads|get_ads_personalization", {})
|
||||
.then(initAds)
|
||||
.catch(() => initAds(true));
|
||||
} else {
|
||||
initAds(true);
|
||||
}
|
||||
} catch (err) {
|
||||
initAds(true);
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
window.addEventListener(
|
||||
"message",
|
||||
(event) => {
|
||||
if (event.data.modrinthAdClick && window.__TAURI_INTERNALS__) {
|
||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|record_ads_click", {});
|
||||
}
|
||||
|
||||
if (event.data.modrinthOpenUrl && window.__TAURI_INTERNALS__) {
|
||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|open_link", {
|
||||
path: event.data.modrinthOpenUrl,
|
||||
origin: event.origin,
|
||||
});
|
||||
}
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
window.addEventListener("mousewheel", (event) => {
|
||||
if (window.__TAURI_INTERNALS__) {
|
||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|scroll_ads_window", {
|
||||
scroll: event.deltaY,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("contextmenu", (event) => event.preventDefault());
|
||||
</script>
|
||||
</body>
|
||||
document.addEventListener("contextmenu", (event) => event.preventDefault());
|
||||
|
||||
const plusLink = document.getElementById("plus-link");
|
||||
plusLink.addEventListener("click", function () {
|
||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|record_ads_click", {});
|
||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|open_link", {
|
||||
path: "https://modrinth.com/plus",
|
||||
origin: "https://modrinth.com",
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
12
packages/app-lib/.sqlx/query-26e3ed8680f6c492b03b458aabfb3f94fddc753b343ef705263188945d0e578d.json
generated
Normal file
12
packages/app-lib/.sqlx/query-26e3ed8680f6c492b03b458aabfb3f94fddc753b343ef705263188945d0e578d.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE settings\n SET\n max_concurrent_writes = $1,\n max_concurrent_downloads = $2,\n\n theme = $3,\n default_page = $4,\n collapsed_navigation = $5,\n advanced_rendering = $6,\n native_decorations = $7,\n\n discord_rpc = $8,\n developer_mode = $9,\n telemetry = $10,\n personalized_ads = $11,\n\n onboarded = $12,\n\n extra_launch_args = jsonb($13),\n custom_env_vars = jsonb($14),\n mc_memory_max = $15,\n mc_force_fullscreen = $16,\n mc_game_resolution_x = $17,\n mc_game_resolution_y = $18,\n hide_on_process_start = $19,\n\n hook_pre_launch = $20,\n hook_wrapper = $21,\n hook_post_exit = $22,\n\n custom_dir = $23,\n prev_custom_dir = $24,\n migrated = $25\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 25
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "26e3ed8680f6c492b03b458aabfb3f94fddc753b343ef705263188945d0e578d"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n theme, default_page, collapsed_navigation, advanced_rendering, native_decorations,\n discord_rpc, developer_mode, telemetry,\n onboarded,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated\n FROM settings\n ",
|
||||
"query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n theme, default_page, collapsed_navigation, advanced_rendering, native_decorations,\n discord_rpc, developer_mode, telemetry, personalized_ads,\n onboarded,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated\n FROM settings\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -54,73 +54,78 @@
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "onboarded",
|
||||
"name": "personalized_ads",
|
||||
"ordinal": 10,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "extra_launch_args",
|
||||
"name": "onboarded",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "custom_env_vars",
|
||||
"name": "extra_launch_args",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "mc_memory_max",
|
||||
"name": "custom_env_vars",
|
||||
"ordinal": 13,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "mc_force_fullscreen",
|
||||
"name": "mc_memory_max",
|
||||
"ordinal": 14,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "mc_game_resolution_x",
|
||||
"name": "mc_force_fullscreen",
|
||||
"ordinal": 15,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "mc_game_resolution_y",
|
||||
"name": "mc_game_resolution_x",
|
||||
"ordinal": 16,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "hide_on_process_start",
|
||||
"name": "mc_game_resolution_y",
|
||||
"ordinal": 17,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "hook_pre_launch",
|
||||
"name": "hide_on_process_start",
|
||||
"ordinal": 18,
|
||||
"type_info": "Text"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "hook_wrapper",
|
||||
"name": "hook_pre_launch",
|
||||
"ordinal": 19,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "hook_post_exit",
|
||||
"name": "hook_wrapper",
|
||||
"ordinal": 20,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "custom_dir",
|
||||
"name": "hook_post_exit",
|
||||
"ordinal": 21,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "prev_custom_dir",
|
||||
"name": "custom_dir",
|
||||
"ordinal": 22,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "migrated",
|
||||
"name": "prev_custom_dir",
|
||||
"ordinal": 23,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "migrated",
|
||||
"ordinal": 24,
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
@@ -139,6 +144,7 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
@@ -154,5 +160,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "03d1aeddf7788320530c447a82342aecdb4099ce183dd9106c4bcc47604cb080"
|
||||
"hash": "8e19c9cdb0aaa48509724e82f6e8f212c9cd2112fdba77cfeee206025af47761"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE settings\n SET\n max_concurrent_writes = $1,\n max_concurrent_downloads = $2,\n\n theme = $3,\n default_page = $4,\n collapsed_navigation = $5,\n advanced_rendering = $6,\n native_decorations = $7,\n\n discord_rpc = $8,\n developer_mode = $9,\n telemetry = $10,\n\n onboarded = $11,\n\n extra_launch_args = jsonb($12),\n custom_env_vars = jsonb($13),\n mc_memory_max = $14,\n mc_force_fullscreen = $15,\n mc_game_resolution_x = $16,\n mc_game_resolution_y = $17,\n hide_on_process_start = $18,\n\n hook_pre_launch = $19,\n hook_wrapper = $20,\n hook_post_exit = $21,\n\n custom_dir = $22,\n prev_custom_dir = $23,\n migrated = $24\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 24
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d645daf951ff6fead3c86df685d99bacc81cb0a999c0f8d2ff7755b0089a79d8"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "theseus"
|
||||
version = "0.8.7"
|
||||
version = "0.8.9"
|
||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -36,7 +36,7 @@ tracing-error = "0.2.0"
|
||||
|
||||
paste = { version = "1.0" }
|
||||
|
||||
tauri = { git = "https://github.com/modrinth/tauri", rev = "5e29428", optional = true }
|
||||
tauri = { version = "2.0.0-rc", optional = true }
|
||||
indicatif = { version = "0.17.3", optional = true }
|
||||
|
||||
async-tungstenite = { version = "0.27.0", features = ["tokio-runtime", "tokio-rustls-webpki-roots"] }
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE settings ADD COLUMN personalized_ads INTEGER NOT NULL DEFAULT TRUE;
|
||||
@@ -17,6 +17,7 @@ pub struct Settings {
|
||||
pub telemetry: bool,
|
||||
pub discord_rpc: bool,
|
||||
pub developer_mode: bool,
|
||||
pub personalized_ads: bool,
|
||||
|
||||
pub onboarded: bool,
|
||||
|
||||
@@ -42,7 +43,7 @@ impl Settings {
|
||||
SELECT
|
||||
max_concurrent_writes, max_concurrent_downloads,
|
||||
theme, default_page, collapsed_navigation, advanced_rendering, native_decorations,
|
||||
discord_rpc, developer_mode, telemetry,
|
||||
discord_rpc, developer_mode, telemetry, personalized_ads,
|
||||
onboarded,
|
||||
json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,
|
||||
mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,
|
||||
@@ -65,6 +66,7 @@ impl Settings {
|
||||
telemetry: res.telemetry == 1,
|
||||
discord_rpc: res.discord_rpc == 1,
|
||||
developer_mode: res.developer_mode == 1,
|
||||
personalized_ads: res.personalized_ads == 1,
|
||||
onboarded: res.onboarded == 1,
|
||||
extra_launch_args: res
|
||||
.extra_launch_args
|
||||
@@ -123,24 +125,25 @@ impl Settings {
|
||||
discord_rpc = $8,
|
||||
developer_mode = $9,
|
||||
telemetry = $10,
|
||||
personalized_ads = $11,
|
||||
|
||||
onboarded = $11,
|
||||
onboarded = $12,
|
||||
|
||||
extra_launch_args = jsonb($12),
|
||||
custom_env_vars = jsonb($13),
|
||||
mc_memory_max = $14,
|
||||
mc_force_fullscreen = $15,
|
||||
mc_game_resolution_x = $16,
|
||||
mc_game_resolution_y = $17,
|
||||
hide_on_process_start = $18,
|
||||
extra_launch_args = jsonb($13),
|
||||
custom_env_vars = jsonb($14),
|
||||
mc_memory_max = $15,
|
||||
mc_force_fullscreen = $16,
|
||||
mc_game_resolution_x = $17,
|
||||
mc_game_resolution_y = $18,
|
||||
hide_on_process_start = $19,
|
||||
|
||||
hook_pre_launch = $19,
|
||||
hook_wrapper = $20,
|
||||
hook_post_exit = $21,
|
||||
hook_pre_launch = $20,
|
||||
hook_wrapper = $21,
|
||||
hook_post_exit = $22,
|
||||
|
||||
custom_dir = $22,
|
||||
prev_custom_dir = $23,
|
||||
migrated = $24
|
||||
custom_dir = $23,
|
||||
prev_custom_dir = $24,
|
||||
migrated = $25
|
||||
",
|
||||
max_concurrent_writes,
|
||||
max_concurrent_downloads,
|
||||
@@ -152,6 +155,7 @@ impl Settings {
|
||||
self.discord_rpc,
|
||||
self.developer_mode,
|
||||
self.telemetry,
|
||||
self.personalized_ads,
|
||||
self.onboarded,
|
||||
extra_launch_args,
|
||||
custom_env_vars,
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
<template>
|
||||
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD">
|
||||
<a href="https://modrinth.com/plus" rel="noopener nofollow sponsored" :target="target"></a>
|
||||
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-0">
|
||||
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-1">
|
||||
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2">
|
||||
<ModrinthPlusIcon class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-3" />
|
||||
<span>
|
||||
<span>
|
||||
90% of ad revenue goes to creators. Go ad-free while supporting creators with
|
||||
</span>
|
||||
<strong>Modrinth Plus.</strong>
|
||||
<span> Subscribe today!</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-4">
|
||||
<a rel="noopener sponsored" :target="target" href="https://adrinth.com"> Ad via Adrinth </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ModrinthPlusIcon } from '@modrinth/assets'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
external: boolean
|
||||
}>(),
|
||||
{
|
||||
external: true,
|
||||
},
|
||||
)
|
||||
|
||||
const target = computed(() => (props.external ? '_blank' : '_self'))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD {
|
||||
position: relative;
|
||||
margin-bottom: var(--gap-md);
|
||||
background: var(--color-ad);
|
||||
border: 1px solid var(--color-ad-raised);
|
||||
border-radius: var(--radius-lg);
|
||||
container-type: inline-size;
|
||||
width: 100%;
|
||||
|
||||
> a {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
&:has(> a:first-child:active) {
|
||||
scale: 0.99;
|
||||
}
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-0 {
|
||||
font-size: 14px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-1 {
|
||||
color: var(--color-base);
|
||||
padding: 1em;
|
||||
text-align: left;
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--color-base);
|
||||
margin-right: 7.5rem;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 b,
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 strong {
|
||||
color: var(--color-ad-highlight);
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-3 {
|
||||
padding-top: 1px;
|
||||
height: 1.5rem;
|
||||
width: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-4 a {
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
right: -1px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8em;
|
||||
color: var(--color-ad-contrast);
|
||||
background: var(--color-ad-raised);
|
||||
letter-spacing: 0.1ch;
|
||||
margin: 0;
|
||||
padding: 2px 10px;
|
||||
border-top-left-radius: var(--radius-lg);
|
||||
border-bottom-right-radius: var(--radius-lg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 a {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -540,8 +540,11 @@ async function refreshPayment(confirmationId, paymentMethodId) {
|
||||
}
|
||||
|
||||
const result = await props.sendBillingRequest({
|
||||
product_id: props.product.id,
|
||||
interval: selectedPlan.value,
|
||||
charge: {
|
||||
type: 'new',
|
||||
product_id: props.product.id,
|
||||
interval: selectedPlan.value,
|
||||
},
|
||||
existing_payment_intent: paymentIntentId.value,
|
||||
...base,
|
||||
})
|
||||
|
||||
@@ -22,7 +22,6 @@ export { default as Page } from './base/Page.vue'
|
||||
export { default as Pagination } from './base/Pagination.vue'
|
||||
export { default as PopoutMenu } from './base/PopoutMenu.vue'
|
||||
export { default as ProjectCard } from './base/ProjectCard.vue'
|
||||
export { default as Promotion } from './base/Promotion.vue'
|
||||
export { default as ScrollablePanel } from './base/ScrollablePanel.vue'
|
||||
export { default as Slider } from './base/Slider.vue'
|
||||
export { default as StatItem } from './base/StatItem.vue'
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import hljs from 'highlight.js/lib/core'
|
||||
// Scripting
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
import python from 'highlight.js/lib/languages/python'
|
||||
import lua from 'highlight.js/lib/languages/lua'
|
||||
import python from 'highlight.js/lib/languages/python'
|
||||
// Coding
|
||||
import groovy from 'highlight.js/lib/languages/groovy'
|
||||
import java from 'highlight.js/lib/languages/java'
|
||||
import kotlin from 'highlight.js/lib/languages/kotlin'
|
||||
import scala from 'highlight.js/lib/languages/scala'
|
||||
import groovy from 'highlight.js/lib/languages/groovy'
|
||||
// Configs
|
||||
import gradle from 'highlight.js/lib/languages/gradle'
|
||||
import json from 'highlight.js/lib/languages/json'
|
||||
import ini from 'highlight.js/lib/languages/ini'
|
||||
import yaml from 'highlight.js/lib/languages/yaml'
|
||||
import xml from 'highlight.js/lib/languages/xml'
|
||||
import json from 'highlight.js/lib/languages/json'
|
||||
import properties from 'highlight.js/lib/languages/properties'
|
||||
import { md, configuredXss } from './parse'
|
||||
import xml from 'highlight.js/lib/languages/xml'
|
||||
import yaml from 'highlight.js/lib/languages/yaml'
|
||||
import { configuredXss, md } from './parse'
|
||||
|
||||
/* REGISTRATION */
|
||||
// Scripting
|
||||
@@ -54,7 +54,7 @@ export const renderHighlightedString = (string) =>
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return hljs.highlight(str, { language: lang }).value
|
||||
} catch (__) {
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
2
packages/utils/index.d.ts
vendored
2
packages/utils/index.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
const BASE62_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' as const
|
||||
export const BASE62_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||
type Base62Char = (typeof BASE62_CHARS)[number]
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -77,7 +77,11 @@ export const configuredXss = new FilterXSS({
|
||||
}
|
||||
},
|
||||
safeAttrValue(tag, name, value, cssFilter) {
|
||||
if (tag === 'img' && name === 'src' && !value.startsWith('data:')) {
|
||||
if (
|
||||
(tag === 'img' || tag === 'video' || tag === 'audio' || tag === 'source') &&
|
||||
(name === 'src' || name === 'srcset') &&
|
||||
!value.startsWith('data:')
|
||||
) {
|
||||
try {
|
||||
const url = new URL(value)
|
||||
|
||||
@@ -112,7 +116,7 @@ export const configuredXss = new FilterXSS({
|
||||
)
|
||||
}
|
||||
return safeAttrValue(tag, name, url.toString(), cssFilter)
|
||||
} catch (err) {
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
@@ -149,7 +153,7 @@ export const md = (options = {}) => {
|
||||
if (allowedHostnames.includes(url.hostname)) {
|
||||
return defaultLinkOpenRenderer(tokens, idx, options, env, self)
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
112
pnpm-lock.yaml
generated
112
pnpm-lock.yaml
generated
@@ -31,6 +31,10 @@ importers:
|
||||
'@modrinth/app-lib':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/app-lib
|
||||
devDependencies:
|
||||
'@tauri-apps/cli':
|
||||
specifier: 2.0.0-rc.16
|
||||
version: 2.0.0-rc.16
|
||||
|
||||
apps/app-frontend:
|
||||
dependencies:
|
||||
@@ -2002,6 +2006,71 @@ packages:
|
||||
'@tauri-apps/api@2.0.0-rc.4':
|
||||
resolution: {integrity: sha512-UNiIhhKG08j4ooss2oEEVexffmWkgkYlC2M3GcX3VPtNsqFgVNL8Mcw/4Y7rO9M9S+ffAMnLOF5ypzyuyb8tyg==}
|
||||
|
||||
'@tauri-apps/cli-darwin-arm64@2.0.0-rc.16':
|
||||
resolution: {integrity: sha512-lISZU4gG0c9PbY7h/j/gW7nJLxZEygNBrYEET6zN8R99Znf5rSO+CfjenaMcJUUj6yTAd8gzdakRpLqNSAWegA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@tauri-apps/cli-darwin-x64@2.0.0-rc.16':
|
||||
resolution: {integrity: sha512-D9uxPCxpyYcTSQulJGFX3POAKPOJd8WcWHFH8x6YVM1cIx6EWRXIE1sZnPUOjFr7qCg+bSdYdr8/BFHcZGcApQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-rc.16':
|
||||
resolution: {integrity: sha512-WsVdKm4D1I1XV8I9yRnmOINZRwwWfh6xcLV3m19+B9g6TohK8RkRxWfxvs3TLQlWOLQ6lo8BzS9rzXB+KtjDpg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-gnu@2.0.0-rc.16':
|
||||
resolution: {integrity: sha512-2jpZDagNs6rrqposHJihHBayttgOl5aB2+bYiVEC6ye+haiFtmYmpdaPOaVIw+YVQs6lepf5PVrisCoU9DmYsg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-musl@2.0.0-rc.16':
|
||||
resolution: {integrity: sha512-SNEDcB+sWOls/B0a+UpUHVa/oegvlXXKYWsTxuXtgWIr5VbWG7rXLZ3fZpLLP3SpRVGTGTnABcyqshFbWSqqKQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-x64-gnu@2.0.0-rc.16':
|
||||
resolution: {integrity: sha512-Zsq29MM1ooeH1+chQBa7ffDFnzAZebBBFdsvs4e05tS1H8gn4oKE+PSMn9p/okzVXykEk9ri2/n7BG1XFeifMA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-x64-musl@2.0.0-rc.16':
|
||||
resolution: {integrity: sha512-g+pwSuis2YMxhJJ/pJYwp/Nps5CWvlv/5MV5UfDvClkCkeAyzIqVX+HbBLPcs5S0CePUQNeP0j4d4jBWUqZZQg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-win32-arm64-msvc@2.0.0-rc.16':
|
||||
resolution: {integrity: sha512-PpPqdMTwJSDAK4KnNjvh77ShSkY+7pih1f6e50EtXar8bjC17e3XcEqFhDNne5mxEVTLYhibs6p1JLPad0ZjRA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@tauri-apps/cli-win32-ia32-msvc@2.0.0-rc.16':
|
||||
resolution: {integrity: sha512-io2yIcEcG7YLP+9n13NbilB93SjcB7jIl8GbURC4XZT4/4t9D1PWHpJr5hySVsGRLCz5e8NzwC5RlnenNzmpPQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@tauri-apps/cli-win32-x64-msvc@2.0.0-rc.16':
|
||||
resolution: {integrity: sha512-Rfkmxe3k+cBVA/kVYt8O25QrQqWKJlH9AiH7Q3C6xBzzG9PCSRMBszCp+JhBF8jhVlwNmOBv6UG+lm85kspwGg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@tauri-apps/cli@2.0.0-rc.16':
|
||||
resolution: {integrity: sha512-wdjZg/M3dcxiqgWG6VRnABpX0dYxRww93t0d1MYoZxFDrnyoUz5kYwFQ0v4J9u0qenEgskjoypvon7V/Nj9qrg==}
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.0.0-rc.0':
|
||||
resolution: {integrity: sha512-DPOXYe8SQ6Radk/67EOdaomlxL7oF99JO/ZUaPp1IBEs3Wro7lhlz63CfdKIBfKIZTLJLzP1R7/EiPL/GTA3Bg==}
|
||||
|
||||
@@ -8041,6 +8110,49 @@ snapshots:
|
||||
|
||||
'@tauri-apps/api@2.0.0-rc.4': {}
|
||||
|
||||
'@tauri-apps/cli-darwin-arm64@2.0.0-rc.16':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-darwin-x64@2.0.0-rc.16':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-rc.16':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-gnu@2.0.0-rc.16':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-musl@2.0.0-rc.16':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-x64-gnu@2.0.0-rc.16':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-x64-musl@2.0.0-rc.16':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-win32-arm64-msvc@2.0.0-rc.16':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-win32-ia32-msvc@2.0.0-rc.16':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-win32-x64-msvc@2.0.0-rc.16':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli@2.0.0-rc.16':
|
||||
optionalDependencies:
|
||||
'@tauri-apps/cli-darwin-arm64': 2.0.0-rc.16
|
||||
'@tauri-apps/cli-darwin-x64': 2.0.0-rc.16
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0-rc.16
|
||||
'@tauri-apps/cli-linux-arm64-gnu': 2.0.0-rc.16
|
||||
'@tauri-apps/cli-linux-arm64-musl': 2.0.0-rc.16
|
||||
'@tauri-apps/cli-linux-x64-gnu': 2.0.0-rc.16
|
||||
'@tauri-apps/cli-linux-x64-musl': 2.0.0-rc.16
|
||||
'@tauri-apps/cli-win32-arm64-msvc': 2.0.0-rc.16
|
||||
'@tauri-apps/cli-win32-ia32-msvc': 2.0.0-rc.16
|
||||
'@tauri-apps/cli-win32-x64-msvc': 2.0.0-rc.16
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.0.0-rc.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-rc.3
|
||||
|
||||
Reference in New Issue
Block a user