Migrate to Nuxt 3 (#933)

* Migrate to Nuxt 3

* Update vercel config

* remove tsconfig comment

* Changelog experiment + working proj pages

* Fix package json

* Prevent vercel complaining

* fix deploy (hopefully)

* Tag generator

* Switch to yarn

* Vercel pls 🙏

* Fix tag generation bug

* Make (most) non-logged in pages work

* fix base build

* Linting + state

* Eradicate axios, make most user pages work

* Fix checkbox state being set incorrectly

* Make most things work

* Final stretch

* Finish (most) things

* Move to update model value

* Fix modal text getting blurred from transforms (#964)

* Adjust nav-link border radius when focused (#961)

* Transition between animation states on TextLogo (#955)

* Transition between animation states on TextLogo

* Remove unused refs

* Fixes from review

* Disable tabbing to pagination arrows when disabled (#972)

* Make position of the "no results" text on grid/gallery views consistent (fixes #963) (#965)

* Fix position of the "no results" text on grid view

* fix padding

* Remove extra margin on main page, fixes #957 (#959)

* Fix layout shift and placeholder line height (#973)

* Fix a lot of issues

* Fix more nuxt 3 issues

* fix not all versions showing up (temp)

* inline inter css file

* More nuxt 3 fixes

* [skip ci] broken- backup changes

* Change modpack warnings to blue instead of red (#991)

* Fix some hydration issues

* Update nuxt

* Fix some images not showing

* Add pagination to versions page + fix lag

* Make changelog page consistent with versions page

* sync before merge

* Delete old file

* Fix actions failing

* update branch

* Fixes navbar transition animation. (#1012)

* Fixes navbar transition animation.

* Fixes Y-axis animation. Fixes mobile menu. Removes highlightjs prop.

* Changes xss call to renderString.

* Fixes renderString call.

* Removes unnecessary styling.

* Reverts mobile nav change.

* Nuxt 3 Lazy Loading Search (#1022)

* Uses lazyFetch for results. onSearchChange refreshes. Adds loading circle.

* Removes console.log

* Preserves old page when paging.

* Diagnosing filtering bugs.

* Fix single facet filtering

* Implements useAuth in settings/account.

* tiny ssr fix

* Updating nuxt.config checklist.

* Implements useAuth in revenue, moneitzation, and dashboard index pages.

* Fixes setups.

* Eliminates results when path changes. Adds animated logo.

* Ensures loading animation renders on search page.

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>

* Fix navigation issues

* Square button fix (#1023)

* Removes checklist from nuxt.config.

* Modifies Nuxt CI to build after linting.

* Fixes prettierignore file.

* bug fixes

* Update whitelist domains

* Page improvements, fix CLS

* Fix a lot of things

* Fix project type redirect

* Fix 404 errors

* Fix user settings + hydration error

* Final fixes

* fix(creator-section): border radius on icons not aligning with bg (#1027)

Co-authored-by: MagnusHJensen <magnus.holm.jensen@lego.dk>

* Improvements to the mobile navbar (#984)

* Transition between animation states on TextLogo

* Remove unused refs

* Fixes from review

* Improvements to the mobile nav menu

* fix avatar alt text

* Nevermind, got confused for a moment

* Tab bar, menu layout improvements

* Highlight search icon when menu is open

* Update layouts/default.vue

Co-authored-by: Magnus Jensen <magnushjensen.mail@gmail.com>

* Fix some issues

* Use caret instead

* Run prettier

* Add create a project

---------

Co-authored-by: Magnus Jensen <magnushjensen.mail@gmail.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>

* Fix mobile menu issues

* More issues

* Fix lint

---------

Co-authored-by: Kaeden Murphy <kmurphy@kaedenmurphy.dev>
Co-authored-by: triphora <emmaffle@modrinth.com>
Co-authored-by: Zach Baird <30800863+ZachBaird@users.noreply.github.com>
Co-authored-by: stairman06 <36215135+stairman06@users.noreply.github.com>
Co-authored-by: Zachary Baird <zdb1994@yahoo.com>
Co-authored-by: Magnus Jensen <magnushjensen.mail@gmail.com>
Co-authored-by: MagnusHJensen <magnus.holm.jensen@lego.dk>
This commit is contained in:
Geometrically
2023-03-09 10:05:32 -07:00
committed by GitHub
parent 5638f0f24b
commit 740357d120
145 changed files with 12371 additions and 37478 deletions

View File

@@ -3,24 +3,16 @@
<section class="universal-card">
<h2>Analytics</h2>
<p>You found a secret!</p>
<nuxt-link to="/frog" class="goto-link"
>Click here for fancy graphs!</nuxt-link
>
<nuxt-link to="/frog" class="goto-link"> Click here for fancy graphs! </nuxt-link>
</section>
</div>
</template>
<script>
export default {
components: {},
data() {
return {}
},
fetch() {},
export default defineNuxtComponent({
head: {
title: 'Analytics - Modrinth',
},
methods: {},
}
})
</script>
<style lang="scss" scoped></style>

View File

@@ -6,11 +6,7 @@
<div class="grid-display__item">
<div class="label">Total downloads</div>
<div class="value">
{{
$formatNumber(
$user.projects.reduce((agg, x) => agg + x.downloads, 0)
)
}}
{{ $formatNumber(user.projects.reduce((agg, x) => agg + x.downloads, 0)) }}
</div>
<span
>from
@@ -27,11 +23,7 @@
<div class="grid-display__item">
<div class="label">Total followers</div>
<div class="value">
{{
$formatNumber(
$user.projects.reduce((agg, x) => agg + x.followers, 0)
)
}}
{{ $formatNumber(user.projects.reduce((agg, x) => agg + x.followers, 0)) }}
</div>
<span>
<span
@@ -49,7 +41,9 @@
</div>
<div class="grid-display__item">
<div class="label">Total revenue</div>
<div class="value">{{ $formatMoney(payouts.all_time) }}</div>
<div class="value">
{{ $formatMoney(payouts.all_time) }}
</div>
<span>{{ $formatMoney(payouts.last_month) }} this month</span>
<!-- <NuxtLink class="goto-link" to="/dashboard/analytics"-->
<!-- >View breakdown-->
@@ -61,17 +55,16 @@
<div class="grid-display__item">
<div class="label">Current balance</div>
<div class="value">
{{ $formatMoney($auth.user.payout_data.balance) }}
{{ $formatMoney(auth.user.payout_data.balance) }}
</div>
<NuxtLink
v-if="$auth.user.payout_data.balance >= minWithdraw"
v-if="auth.user.payout_data.balance >= minWithdraw"
class="goto-link"
to="/dashboard/revenue"
>Withdraw earnings
<ChevronRightIcon
class="featured-header-chevron"
aria-hidden="true"
/></NuxtLink>
>
Withdraw earnings
<ChevronRightIcon class="featured-header-chevron" aria-hidden="true" />
</NuxtLink>
<span v-else>${{ minWithdraw }} is the withdraw minimum</span>
</div>
</div>
@@ -79,55 +72,37 @@
<section class="universal-card more-soon">
<h2>More coming soon!</h2>
<p>
Stay tuned for more metrics and analytics (pretty graphs, anyone? 👀)
coming to the creators dashboard soon!
Stay tuned for more metrics and analytics (pretty graphs, anyone? 👀) coming to the creators
dashboard soon!
</p>
</section>
</div>
</template>
<script setup>
import ChevronRightIcon from '~/assets/images/utils/chevron-right.svg'
<script>
import ChevronRightIcon from '~/assets/images/utils/chevron-right.svg?inline'
useHead({
title: 'Creator dashboard - Modrinth',
})
export default {
components: { ChevronRightIcon },
async asyncData(data) {
const [payouts] = (
await Promise.all([
data.$axios.get(
`user/${data.$auth.user.id}/payouts`,
data.$defaultHeaders()
),
])
).map((it) => it.data)
const auth = await useAuth()
const app = useNuxtApp()
payouts.all_time = Math.floor(payouts.all_time * 100) / 100
payouts.last_month = Math.floor(payouts.last_month * 100) / 100
const [raw] = await Promise.all([
useBaseFetch(`user/${auth.value.user.id}/payouts`, app.$defaultHeaders()),
])
const user = await useUser()
return {
payouts,
}
},
data() {
return {
minWithdraw: 0.26,
}
},
fetch() {},
head: {
title: 'Creator dashboard - Modrinth',
},
computed: {
downloadsProjectCount() {
return this.$user.projects.filter((project) => project.downloads > 0)
.length
},
followersProjectCount() {
return this.$user.projects.filter((project) => project.followers > 0)
.length
},
},
methods: {},
}
raw.all_time = Math.floor(raw.all_time * 100) / 100
raw.last_month = Math.floor(raw.last_month * 100) / 100
const payouts = ref(raw)
const minWithdraw = ref(0.26)
const downloadsProjectCount = computed(
() => user.value.projects.filter((project) => project.downloads > 0).length
)
const followersProjectCount = computed(
() => user.value.projects.filter((project) => project.followers > 0).length
)
</script>
<style lang="scss" scoped></style>

View File

@@ -3,9 +3,9 @@
<Modal ref="editLinksModal" header="Edit links">
<div class="universal-modal links-modal">
<p>
Any links you specify below will be overwritten on each of the
selected projects. Any you leave blank will be ignored. You can clear
a link from all selected projects using the trash can button.
Any links you specify below will be overwritten on each of the selected projects. Any you
leave blank will be ignored. You can clear a link from all selected projects using the
trash can button.
</p>
<section class="links">
<label
@@ -21,14 +21,13 @@
:disabled="editLinks.issues.clear"
type="url"
:placeholder="
editLinks.issues.clear
? 'Existing link will be cleared'
: 'Enter a valid URL'
editLinks.issues.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
"
maxlength="2048"
/>
<button
v-tooltip="'Clear link'"
aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.issues.clear"
@click="editLinks.issues.clear = !editLinks.issues.clear"
@@ -50,13 +49,12 @@
type="url"
maxlength="2048"
:placeholder="
editLinks.source.clear
? 'Existing link will be cleared'
: 'Enter a valid URL'
editLinks.source.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
"
/>
<button
v-tooltip="'Clear link'"
aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.source.clear"
@click="editLinks.source.clear = !editLinks.source.clear"
@@ -78,13 +76,12 @@
type="url"
maxlength="2048"
:placeholder="
editLinks.wiki.clear
? 'Existing link will be cleared'
: 'Enter a valid URL'
editLinks.wiki.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
"
/>
<button
v-tooltip="'Clear link'"
aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.wiki.clear"
@click="editLinks.wiki.clear = !editLinks.wiki.clear"
@@ -92,10 +89,7 @@
<TrashIcon />
</button>
</div>
<label
for="discord-invite-input"
title="An invitation link to your Discord server."
>
<label for="discord-invite-input" title="An invitation link to your Discord server.">
<span class="label__title">Discord invite</span>
</label>
<div class="input-group shrink-first">
@@ -113,6 +107,7 @@
/>
<button
v-tooltip="'Clear link'"
aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.discord.clear"
@click="editLinks.discord.clear = !editLinks.discord.clear"
@@ -154,10 +149,7 @@
<CrossIcon />
Cancel
</button>
<button
class="iconified-button brand-button"
@click="bulkEditLinks()"
>
<button class="iconified-button brand-button" @click="bulkEditLinks()">
<SaveIcon />
Save changes
</button>
@@ -169,10 +161,7 @@
<div class="header__row">
<h2 class="header__title">Projects</h2>
<div class="input-group">
<button
class="iconified-button brand-button"
@click="$refs.modal_creation.show()"
>
<button class="iconified-button brand-button" @click="$refs.modal_creation.show()">
<PlusIcon />
Create a project
</button>
@@ -203,8 +192,8 @@
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
@input="updateSort()"
></Multiselect>
@update:model-value="projects = updateSort(projects, sortBy)"
/>
</div>
</div>
</div>
@@ -212,8 +201,8 @@
<div class="grid-table__row grid-table__header">
<div>
<Checkbox
:value="selectedProjects === projects"
@input="
:model-value="selectedProjects === projects"
@update:model-value="
selectedProjects === projects
? (selectedProjects = [])
: (selectedProjects = projects)
@@ -225,33 +214,22 @@
<div>ID</div>
<div>Type</div>
<div>Status</div>
<div></div>
<div />
</div>
<div
v-for="project in projects"
:key="`project-${project.id}`"
class="grid-table__row"
>
<div v-for="project in projects" :key="`project-${project.id}`" class="grid-table__row">
<div>
<Checkbox
:disabled="
(project.permissions & EDIT_DETAILS) === EDIT_DETAILS
"
:value="selectedProjects.includes(project)"
@input="
:disabled="(project.permissions & EDIT_DETAILS) === EDIT_DETAILS"
:model-value="selectedProjects.includes(project)"
@update:model-value="
selectedProjects.includes(project)
? (selectedProjects = selectedProjects.filter(
(it) => it !== project
))
? (selectedProjects = selectedProjects.filter((it) => it !== project))
: selectedProjects.push(project)
"
/>
</div>
<div>
<nuxt-link
tabindex="-1"
:to="`/${project.project_type}/${project.slug}`"
>
<nuxt-link tabindex="-1" :to="`/${project.project_type}/${project.slug}`">
<Avatar
:src="project.icon_url"
aria-hidden="true"
@@ -265,9 +243,6 @@
<span class="project-title">
<IssuesIcon
v-if="project.moderator_message"
v-tooltip="
'Project has a message from the moderators. View the project to see more.'
"
aria-label="Project has a message from the moderators. View the project to see more."
/>
@@ -285,15 +260,11 @@
</div>
<div>
{{ $formatProjectType(project.project_type) }}
{{ $formatProjectType($getProjectTypeForUrl(project.project_type, project.loaders)) }}
</div>
<div>
<Badge
v-if="project.status"
:type="project.status"
class="status"
/>
<Badge v-if="project.status" :type="project.status" class="status" />
</div>
<div>
@@ -317,20 +288,19 @@ import Multiselect from 'vue-multiselect'
import Badge from '~/components/ui/Badge.vue'
import Checkbox from '~/components/ui/Checkbox.vue'
import Modal from '~/components/ui/Modal.vue'
// import ModalConfirm from '~/components/ui/ModalConfirm.vue'
import Avatar from '~/components/ui/Avatar.vue'
import ModalCreation from '~/components/ui/ModalCreation.vue'
import CopyCode from '~/components/ui/CopyCode.vue'
import SettingsIcon from '~/assets/images/utils/settings.svg?inline'
import TrashIcon from '~/assets/images/utils/trash.svg?inline'
import IssuesIcon from '~/assets/images/utils/issues.svg?inline'
import PlusIcon from '~/assets/images/utils/plus.svg?inline'
import CrossIcon from '~/assets/images/utils/x.svg?inline'
import EditIcon from '~/assets/images/utils/edit.svg?inline'
import SaveIcon from '~/assets/images/utils/save.svg?inline'
import SettingsIcon from '~/assets/images/utils/settings.svg'
import TrashIcon from '~/assets/images/utils/trash.svg'
import IssuesIcon from '~/assets/images/utils/issues.svg'
import PlusIcon from '~/assets/images/utils/plus.svg'
import CrossIcon from '~/assets/images/utils/x.svg'
import EditIcon from '~/assets/images/utils/edit.svg'
import SaveIcon from '~/assets/images/utils/save.svg'
export default {
export default defineNuxtComponent({
components: {
Avatar,
Badge,
@@ -343,14 +313,21 @@ export default {
EditIcon,
SaveIcon,
Modal,
// ModalConfirm,
ModalCreation,
Multiselect,
CopyCode,
},
async setup() {
const user = await useUser()
if (process.client) {
await initUserProjects()
}
return { user: ref(user) }
},
data() {
return {
projects: [],
projects: this.updateSort(this.user.projects, 'Name'),
versions: [],
selectedProjects: [],
sortBy: 'Name',
@@ -375,10 +352,6 @@ export default {
},
}
},
fetch() {
this.projects = this.$user.projects
this.updateSort()
},
head: {
title: 'Projects - Modrinth',
},
@@ -392,12 +365,11 @@ export default {
this.EDIT_MEMBER = 1 << 6
this.DELETE_PROJECT = 1 << 7
},
mounted() {},
methods: {
updateSort() {
switch (this.sortBy) {
updateSort(projects, sort) {
switch (sort) {
case 'Name':
this.projects = this.projects.slice().sort((a, b) => {
return projects.slice().sort((a, b) => {
if (a.title < b.title) {
return -1
}
@@ -406,9 +378,8 @@ export default {
}
return 0
})
break
case 'Status':
this.projects = this.projects.slice().sort((a, b) => {
return projects.slice().sort((a, b) => {
if (a.status < b.status) {
return -1
}
@@ -417,9 +388,8 @@ export default {
}
return 0
})
break
case 'Type':
this.projects = this.projects.slice().sort((a, b) => {
return projects.slice().sort((a, b) => {
if (a.project_type < b.project_type) {
return -1
}
@@ -428,7 +398,6 @@ export default {
}
return 0
})
break
default:
break
}
@@ -437,13 +406,11 @@ export default {
try {
const baseData = {
issues_url:
!this.editLinks.issues.clear &&
this.editLinks.issues.val.trim() !== ''
!this.editLinks.issues.clear && this.editLinks.issues.val.trim() !== ''
? this.editLinks.issues.val
: null,
source_url:
!this.editLinks.source.clear &&
this.editLinks.source.val.trim() !== ''
!this.editLinks.source.clear && this.editLinks.source.val.trim() !== ''
? this.editLinks.source.val
: null,
wiki_url:
@@ -451,18 +418,18 @@ export default {
? this.editLinks.wiki.val
: null,
discord_url:
!this.editLinks.discord.clear &&
this.editLinks.discord.val.trim() !== ''
!this.editLinks.discord.clear && this.editLinks.discord.val.trim() !== ''
? this.editLinks.discord.val
: null,
}
await this.$axios.patch(
`projects?ids=${JSON.stringify(
this.selectedProjects.map((x) => x.id)
)}`,
baseData,
this.$defaultHeaders()
await useBaseFetch(
`projects?ids=${JSON.stringify(this.selectedProjects.map((x) => x.id))}`,
{
method: 'PATCH',
body: baseData,
...this.$defaultHeaders(),
}
)
this.$refs.editLinksModal.hide()
@@ -483,7 +450,7 @@ export default {
}
},
},
}
})
</script>
<style lang="scss" scoped>
.grid-table {

View File

@@ -3,47 +3,42 @@
<ModalTransfer
v-if="enrolled"
ref="modal_transfer"
:wallet="$auth.user.payout_data.payout_wallet"
:account-type="$auth.user.payout_data.payout_wallet_type"
:account="$auth.user.payout_data.payout_address"
:balance="$auth.user.payout_data.balance"
:wallet="auth.user.payout_data.payout_wallet"
:account-type="auth.user.payout_data.payout_wallet_type"
:account="auth.user.payout_data.payout_address"
:balance="auth.user.payout_data.balance"
:min-withdraw="minWithdraw"
/>
<section class="universal-card">
<h2>Withdraw</h2>
<div v-if="$auth.user.payout_data.balance >= minWithdraw">
<div v-if="auth.user.payout_data.balance >= minWithdraw">
<p>
You have
<strong>{{ $formatMoney($auth.user.payout_data.balance) }}</strong>
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong>
available to withdraw.
<span v-if="!enrolled"
>Enroll in the Creator Monetization Program to withdraw your
revenue.</span
>Enroll in the Creator Monetization Program to withdraw your revenue.</span
>
</p>
<div v-if="enrolled" class="input-group">
<button
class="iconified-button brand-button"
@click="$refs.modal_transfer.show()"
>
<button class="iconified-button brand-button" @click="$refs.modal_transfer.show()">
<TransferIcon /> Transfer to
{{ $formatWallet($auth.user.payout_data.payout_wallet) }}
{{ $formatWallet(auth.user.payout_data.payout_wallet) }}
</button>
<NuxtLink class="iconified-button" to="/settings/monetization">
<SettingsIcon /> Monetization settings
</NuxtLink>
</div>
</div>
<p v-else-if="$auth.user.payout_data.balance > 0">
<p v-else-if="auth.user.payout_data.balance > 0">
You have made
<strong>{{ $formatMoney($auth.user.payout_data.balance) }}</strong
>, however you have not yet met the minimum of ${{ minWithdraw }} to
withdraw.
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong
>, however you have not yet met the minimum of ${{ minWithdraw }} to withdraw.
</p>
<p v-else>
You have made
<strong>{{ $formatMoney($auth.user.payout_data.balance) }}</strong
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong
>, which is under the minimum of ${{ minWithdraw }} to withdraw.
</p>
<div v-if="!enrolled">
@@ -55,9 +50,9 @@
<section class="universal-card">
<h2>Processing fees</h2>
<p>
To avoid paying unnecessary fee deductions, you may want to wait to
transfer your money out after it accumulates for a bit rather than
transferring as soon as you reach the minimum of ${{ minWithdraw }}.
To avoid paying unnecessary fee deductions, you may want to wait to transfer your money out
after it accumulates for a bit rather than transferring as soon as you reach the minimum of
${{ minWithdraw }}.
</p>
<h3>PayPal</h3>
<ul>
@@ -67,55 +62,57 @@
fee per transaction.
</li>
<li>
In the rest of the world, PayPal charges a <strong>2%</strong> (up to
$20) fee per transaction.
In the rest of the world, PayPal charges a <strong>2%</strong> (up to $20) fee per
transaction.
</li>
</ul>
<p>
Modrinth will deduct <strong>2%</strong> for the fee (minimum of $0.25
and maximum of $20) from <strong>all transfers</strong> and if the fee
PayPal charges is less than the amount we deducted, the difference will
be added back to your Modrinth balance. This happens as Modrinth cannot
determine if a transaction will be in the United States or international
or not until after the transaction has been made.
Modrinth will deduct <strong>2%</strong> for the fee (minimum of $0.25 and maximum of $20)
from <strong>all transfers</strong> and if the fee PayPal charges is less than the amount we
deducted, the difference will be added back to your Modrinth balance. This happens as
Modrinth cannot determine if a transaction will be in the United States or international or
not until after the transaction has been made.
</p>
<h3>Venmo (United States only)</h3>
<p>
Venmo will charge a $0.25 processing fee per transaction, which will be
deducted from the amount you choose to transfer.
Venmo will charge a $0.25 processing fee per transaction, which will be deducted from the
amount you choose to transfer.
</p>
<h2>Currency conversions</h2>
<p>
All revenue generated by Modrinth is in United States dollars. Any
conversions to your local currency will happen at withdrawal and is not
handled by Modrinth. Modrinth cannot guarantee any exchange rate, so
only USD is displayed in the creator dashboard.
All revenue generated by Modrinth is in United States dollars. Any conversions to your local
currency will happen at withdrawal and is not handled by Modrinth. Modrinth cannot guarantee
any exchange rate, so only USD is displayed in the creator dashboard.
</p>
</section>
</div>
</template>
<script>
import TransferIcon from '~/assets/images/utils/transfer.svg?inline'
import SettingsIcon from '~/assets/images/utils/settings.svg?inline'
import TransferIcon from '~/assets/images/utils/transfer.svg'
import SettingsIcon from '~/assets/images/utils/settings.svg'
import ModalTransfer from '~/components/ui/ModalTransfer'
export default {
export default defineNuxtComponent({
components: { TransferIcon, SettingsIcon, ModalTransfer },
async setup() {
const auth = await useAuth()
return { auth }
},
data() {
return {
minWithdraw: 0.26,
enrolled:
this.$auth.user.payout_data.payout_wallet &&
this.$auth.user.payout_data.payout_wallet_type &&
this.$auth.user.payout_data.payout_address,
this.auth.user.payout_data.payout_wallet &&
this.auth.user.payout_data.payout_wallet_type &&
this.auth.user.payout_data.payout_address,
}
},
head: {
title: 'Revenue - Modrinth',
},
methods: {},
}
})
</script>
<style lang="scss" scoped>
strong {