feat: mv profile to footer
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,7 +2,6 @@
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
@@ -18,10 +18,22 @@ const fontSizes = Array.from({ length: 7 }, (_, i) => `${9 + i * 2}pt`)
|
||||
<div class="font-change" v-if="sortedFontFamilies.length > 0">
|
||||
<theme-swap />
|
||||
|
||||
<label class="font-label">t</label>
|
||||
<select
|
||||
class="select"
|
||||
:value="store.userSettings?.chosenFontFamily"
|
||||
@change="store.setFontFamily(($event.target as HTMLSelectElement).value)"
|
||||
:value="store.userSettings?.chosenTitleFont"
|
||||
@change="store.setTitleFont(($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option v-for="font in sortedFontFamilies" :key="font" :value="font">
|
||||
{{ font }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<label class="font-label">p</label>
|
||||
<select
|
||||
class="select"
|
||||
:value="store.userSettings?.chosenBodyFont"
|
||||
@change="store.setBodyFont(($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option v-for="font in sortedFontFamilies" :key="font" :value="font">
|
||||
{{ font }}
|
||||
@@ -53,4 +65,10 @@ const fontSizes = Array.from({ length: 7 }, (_, i) => `${9 + i * 2}pt`)
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.font-label {
|
||||
font-weight: bold;
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,11 +3,13 @@ import RepoList from "@/components/RepoList.vue"
|
||||
import SignInAtproto from "@/components/SignInAtproto.vue"
|
||||
import SignInGithub from "@/components/SignInGithub.vue"
|
||||
import ThemeSwap from "@/components/ThemeSwap.vue"
|
||||
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
||||
import { useForm } from "@/hooks/useForm.hook"
|
||||
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
|
||||
import LastVisited from "@/modules/history/components/LastVisited.vue"
|
||||
|
||||
const { isLogged } = useGitHubLogin()
|
||||
const { isLoggedIn: isATProtoLoggedIn, avatarUrl } = useATProtoLogin()
|
||||
const { userInput, repoInput, submit } = useForm()
|
||||
</script>
|
||||
|
||||
@@ -22,13 +24,6 @@ const { userInput, repoInput, submit } = useForm()
|
||||
|
||||
<last-visited />
|
||||
|
||||
<div class="get-started">
|
||||
<sign-in-github />
|
||||
<router-link v-if="isLogged" :to="{ name: 'RepoList' }" class="btn btn-sm"
|
||||
>Manage your repos</router-link
|
||||
>
|
||||
</div>
|
||||
|
||||
<form class="github-form" @submit.prevent>
|
||||
<div>github/</div>
|
||||
<input
|
||||
@@ -71,7 +66,32 @@ const { userInput, repoInput, submit } = useForm()
|
||||
<a href="https://apoena.dev" target="_blank" rel="noopener noreferrer"
|
||||
>apoena</a
|
||||
>
|
||||
<sign-in-atproto :with-sign-out="false" />
|
||||
<button
|
||||
class="btn btn-ghost btn-circle btn-sm profile-btn"
|
||||
onclick="profile_modal.showModal()"
|
||||
>
|
||||
<img
|
||||
v-if="isATProtoLoggedIn && avatarUrl"
|
||||
:src="avatarUrl"
|
||||
class="profile-avatar"
|
||||
alt="Profile"
|
||||
/>
|
||||
<svg
|
||||
v-else
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="28"
|
||||
height="28"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" />
|
||||
<path d="M6 20c0 -2.21 2.686 -4 6 -4s6 1.79 6 4" />
|
||||
</svg>
|
||||
</button>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'FluxNoteView',
|
||||
@@ -81,6 +101,28 @@ const { userInput, repoInput, submit } = useForm()
|
||||
>Get started</router-link
|
||||
>
|
||||
</footer>
|
||||
|
||||
<dialog id="profile_modal" class="modal">
|
||||
<div class="modal-box profile-modal-box">
|
||||
<h3 class="text-lg font-bold">Profile</h3>
|
||||
<div class="profile-section">
|
||||
<sign-in-atproto :with-sign-out="true" />
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="profile-section">
|
||||
<sign-in-github />
|
||||
<router-link
|
||||
v-if="isLogged"
|
||||
:to="{ name: 'RepoList' }"
|
||||
class="btn btn-sm"
|
||||
>Manage your repos</router-link
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button></button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -105,19 +147,11 @@ h1 {
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.get-started {
|
||||
margin: center;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.github-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -127,15 +161,31 @@ h1 {
|
||||
max-width: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
margin-top: 0;
|
||||
}
|
||||
.profile-avatar {
|
||||
max-width: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.profile-modal-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.profile-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.to-user-repo {
|
||||
|
||||
@@ -14,14 +14,32 @@ import {
|
||||
|
||||
const did = ref<string | null>(null)
|
||||
const handle = ref<string | null>(null)
|
||||
const avatarUrl = ref<string | null>(null)
|
||||
|
||||
let init = true
|
||||
|
||||
const fetchAvatar = async (actorDid: string) => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(actorDid)}`
|
||||
)
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
avatarUrl.value = data.avatar ?? null
|
||||
}
|
||||
} catch {
|
||||
avatarUrl.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const initializeAuth = async () => {
|
||||
// Load cached session from IndexedDB first (fast, local) so the UI can render immediately
|
||||
const stored = await loadSession()
|
||||
did.value = stored?.did ?? ""
|
||||
handle.value = stored?.handle ?? ""
|
||||
if (stored?.did) {
|
||||
fetchAvatar(stored.did)
|
||||
}
|
||||
|
||||
// Then restore OAuth session in the background (may involve network)
|
||||
const session = await restoreSession()
|
||||
@@ -32,6 +50,7 @@ const initializeAuth = async () => {
|
||||
did.value = session.did
|
||||
handle.value = resolvedHandle
|
||||
await saveSession(session.did, resolvedHandle)
|
||||
fetchAvatar(session.did)
|
||||
|
||||
window.history.replaceState(
|
||||
null,
|
||||
@@ -61,11 +80,13 @@ export const useATProtoLogin = () => {
|
||||
await clearSession()
|
||||
did.value = ""
|
||||
handle.value = ""
|
||||
avatarUrl.value = null
|
||||
}
|
||||
|
||||
return {
|
||||
did,
|
||||
handle,
|
||||
avatarUrl,
|
||||
isLoggedIn,
|
||||
isATProtoReady,
|
||||
signIn,
|
||||
|
||||
@@ -8,4 +8,6 @@ export interface UserSettings extends Model<DataType.UserSettings> {
|
||||
fontSize?: string
|
||||
chosenFontSize?: string
|
||||
backlink?: boolean
|
||||
chosenTitleFont?: string
|
||||
chosenBodyFont?: string
|
||||
}
|
||||
|
||||
@@ -104,7 +104,16 @@ export const getUserSettingsContent = async (
|
||||
return null
|
||||
}
|
||||
|
||||
return JSON.parse(atob(content)) as UserSettings
|
||||
const raw = JSON.parse(atob(content)) as UserSettings & {
|
||||
t?: string
|
||||
p?: string
|
||||
}
|
||||
const { t, p, ...rest } = raw
|
||||
return {
|
||||
...rest,
|
||||
chosenTitleFont: t,
|
||||
chosenBodyFont: p
|
||||
}
|
||||
}
|
||||
|
||||
export const queryFileContent = async (
|
||||
|
||||
@@ -81,6 +81,14 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
||||
: userSettings?.fontFamily
|
||||
const chosenFontSize =
|
||||
this.userSettings?.chosenFontSize ?? userSettings?.fontSize
|
||||
const chosenTitleFont =
|
||||
this.userSettings?.chosenTitleFont ??
|
||||
userSettings?.chosenTitleFont ??
|
||||
chosenFontFamily
|
||||
const chosenBodyFont =
|
||||
this.userSettings?.chosenBodyFont ??
|
||||
userSettings?.chosenBodyFont ??
|
||||
chosenFontFamily
|
||||
this.userSettings = userSettings
|
||||
|
||||
if (!this.userSettings) {
|
||||
@@ -91,6 +99,8 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
||||
chosenFontFamily ?? this.userSettings.fontFamily
|
||||
this.userSettings.chosenFontSize =
|
||||
chosenFontSize ?? this.userSettings.fontSize
|
||||
this.userSettings.chosenTitleFont = chosenTitleFont
|
||||
this.userSettings.chosenBodyFont = chosenBodyFont
|
||||
|
||||
data.update<DataType.UserSettings, UserSettings>({
|
||||
...this.userSettings,
|
||||
@@ -156,6 +166,30 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
||||
}
|
||||
this.userSettings.chosenFontSize = fontSize
|
||||
|
||||
const userSettingsId = `UserSetting-${this.user}-${this.repo}`
|
||||
data.update<DataType.UserSettings, UserSettings>({
|
||||
...this.userSettings,
|
||||
_id: userSettingsId
|
||||
})
|
||||
},
|
||||
setTitleFont(font: string) {
|
||||
if (!this.userSettings) {
|
||||
return
|
||||
}
|
||||
this.userSettings.chosenTitleFont = font
|
||||
|
||||
const userSettingsId = `UserSetting-${this.user}-${this.repo}`
|
||||
data.update<DataType.UserSettings, UserSettings>({
|
||||
...this.userSettings,
|
||||
_id: userSettingsId
|
||||
})
|
||||
},
|
||||
setBodyFont(font: string) {
|
||||
if (!this.userSettings) {
|
||||
return
|
||||
}
|
||||
this.userSettings.chosenBodyFont = font
|
||||
|
||||
const userSettingsId = `UserSetting-${this.user}-${this.repo}`
|
||||
data.update<DataType.UserSettings, UserSettings>({
|
||||
...this.userSettings,
|
||||
|
||||
@@ -12,10 +12,15 @@ export const useUserSettings = () => {
|
||||
watchEffect(() => {
|
||||
const root = document.documentElement
|
||||
|
||||
const fontFamily = store.userSettings?.chosenFontFamily
|
||||
const fontSize = store.userSettings?.chosenFontSize
|
||||
const bodyFont = store.userSettings?.chosenBodyFont
|
||||
const titleFont = store.userSettings?.chosenTitleFont
|
||||
|
||||
downloadFont(fontFamily || DEFAULT_FONT_POLICY)
|
||||
downloadFont(bodyFont || DEFAULT_FONT_POLICY, "--font-family")
|
||||
downloadFont(
|
||||
titleFont || bodyFont || DEFAULT_FONT_POLICY,
|
||||
"--title-font-family"
|
||||
)
|
||||
root.style.setProperty("--font-size", fontSize || DEFAULT_FONT_SIZE)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
:root {
|
||||
--primary-color: #ffa4c0;
|
||||
--font-family: "Libertinus Serif", serif;
|
||||
--title-font-family: "Libertinus Serif", serif;
|
||||
--font-size: 13pt;
|
||||
--font-color: #4a4a4a;
|
||||
--link: #445fb9;
|
||||
|
||||
@@ -6,7 +6,10 @@ const assembleFontLink = (font: string) => {
|
||||
.replaceAll(" ", "+")}`
|
||||
}
|
||||
|
||||
export const downloadFont = async (font: string): Promise<void> => {
|
||||
export const downloadFont = async (
|
||||
font: string,
|
||||
cssVar = "--font-family"
|
||||
): Promise<void> => {
|
||||
const href = assembleFontLink(font)
|
||||
|
||||
// check if the href already exists
|
||||
@@ -23,7 +26,7 @@ export const downloadFont = async (font: string): Promise<void> => {
|
||||
try {
|
||||
await new FontFaceObserver(font).load()
|
||||
|
||||
document.documentElement.style.setProperty("--font-family", font)
|
||||
document.documentElement.style.setProperty(cssVar, font)
|
||||
} catch (error) {
|
||||
console.warn("error when loading font")
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ const defaultTitleStyles = Array.from(
|
||||
...acc,
|
||||
[heading]: {
|
||||
"margin-top": "0",
|
||||
"margin-bottom": "0.5em"
|
||||
"margin-bottom": "0.5em",
|
||||
"font-family": "var(--title-font-family)"
|
||||
}
|
||||
}),
|
||||
{}
|
||||
|
||||
Reference in New Issue
Block a user