This commit is contained in:
cutestnekoaqua 2023-04-04 22:15:56 +02:00
parent d4d9065781
commit a0291b40b2
No known key found for this signature in database
GPG Key ID: 6BF0964A5069C1E0
26 changed files with 505 additions and 63 deletions

View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://maven.google.com" />
</remote-repository>
</component>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -9,10 +9,15 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies { dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-camera')
implementation project(':capacitor-device')
implementation project(':capacitor-preferences')
implementation project(':capacitor-push-notifications')
implementation project(':capacitor-status-bar')
implementation "com.onesignal:OneSignal:4.8.5"
} }
apply from: "../../../../node_modules/.pnpm/onesignal-cordova-plugin@3.3.1/node_modules/onesignal-cordova-plugin/build-extras-onesignal.gradle"
if (hasProperty('postBuildExtras')) { if (hasProperty('postBuildExtras')) {
postBuildExtras() postBuildExtras()

View File

@ -1,6 +1,13 @@
package org.calckey.app; package org.calckey.app;
import android.content.res.Configuration;
import android.os.Bundle;
import android.webkit.WebSettings;
import com.getcapacitor.BridgeActivity; import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;
import java.util.ArrayList;
public class MainActivity extends BridgeActivity { public class MainActivity extends BridgeActivity {
void setDarkMode() { void setDarkMode() {
@ -34,15 +41,4 @@ public class MainActivity extends BridgeActivity {
super.onResume(); super.onResume();
setDarkMode(); setDarkMode();
} }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Initializes the Bridge
this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
// Additional plugins you've installed go here
// Ex: add(TotallyAwesomePlugin.class);
}});
}
} }

View File

@ -1,3 +1,21 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android' include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@4.7.3_@capacitor+core@4.7.3/node_modules/@capacitor/android/capacitor') project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@4.7.3_@capacitor+core@4.7.3/node_modules/@capacitor/android/capacitor')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@4.1.1_@capacitor+core@4.7.3/node_modules/@capacitor/app/android')
include ':capacitor-camera'
project(':capacitor-camera').projectDir = new File('../../../node_modules/.pnpm/@capacitor+camera@4.1.4_@capacitor+core@4.7.3/node_modules/@capacitor/camera/android')
include ':capacitor-device'
project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@4.1.0_@capacitor+core@4.7.3/node_modules/@capacitor/device/android')
include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../../../node_modules/.pnpm/@capacitor+preferences@4.0.2_@capacitor+core@4.7.3/node_modules/@capacitor/preferences/android')
include ':capacitor-push-notifications'
project(':capacitor-push-notifications').projectDir = new File('../../../node_modules/.pnpm/@capacitor+push-notifications@4.1.2_@capacitor+core@4.7.3/node_modules/@capacitor/push-notifications/android')
include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../../../node_modules/.pnpm/@capacitor+status-bar@4.1.1_@capacitor+core@4.7.3/node_modules/@capacitor/status-bar/android')

View File

@ -16,6 +16,10 @@
<string>$(PRODUCT_NAME)</string> <string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>

View File

@ -11,7 +11,13 @@ install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods def capacitor_pods
pod 'Capacitor', :path => '../../../../node_modules/.pnpm/@capacitor+ios@4.7.3_@capacitor+core@4.7.3/node_modules/@capacitor/ios' pod 'Capacitor', :path => '../../../../node_modules/.pnpm/@capacitor+ios@4.7.3_@capacitor+core@4.7.3/node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../../../node_modules/.pnpm/@capacitor+ios@4.7.3_@capacitor+core@4.7.3/node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../../../node_modules/.pnpm/@capacitor+ios@4.7.3_@capacitor+core@4.7.3/node_modules/@capacitor/ios'
pod 'CapacitorApp', :path => '../../../../node_modules/.pnpm/@capacitor+app@4.1.1_@capacitor+core@4.7.3/node_modules/@capacitor/app'
pod 'CapacitorCamera', :path => '../../../../node_modules/.pnpm/@capacitor+camera@4.1.4_@capacitor+core@4.7.3/node_modules/@capacitor/camera'
pod 'CapacitorDevice', :path => '../../../../node_modules/.pnpm/@capacitor+device@4.1.0_@capacitor+core@4.7.3/node_modules/@capacitor/device'
pod 'CapacitorPreferences', :path => '../../../../node_modules/.pnpm/@capacitor+preferences@4.0.2_@capacitor+core@4.7.3/node_modules/@capacitor/preferences'
pod 'CapacitorPushNotifications', :path => '../../../../node_modules/.pnpm/@capacitor+push-notifications@4.1.2_@capacitor+core@4.7.3/node_modules/@capacitor/push-notifications'
pod 'CapacitorStatusBar', :path => '../../../../node_modules/.pnpm/@capacitor+status-bar@4.1.1_@capacitor+core@4.7.3/node_modules/@capacitor/status-bar'
pod 'CordovaPluginsStatic', :path => '../capacitor-cordova-ios-plugins'
end end
target 'App' do target 'App' do

View File

@ -95,6 +95,7 @@
"dependencies": { "dependencies": {
"@capacitor/android": "^4.7.3", "@capacitor/android": "^4.7.3",
"@capacitor/app": "^4.1.1", "@capacitor/app": "^4.1.1",
"@capacitor/camera": "^4.1.4",
"@capacitor/core": "^4.7.3", "@capacitor/core": "^4.7.3",
"@capacitor/device": "^4.1.0", "@capacitor/device": "^4.1.0",
"@capacitor/ios": "^4.7.3", "@capacitor/ios": "^4.7.3",

View File

@ -4,6 +4,7 @@ import { showSuspendedDialog } from "./scripts/show-suspended-dialog";
import { i18n } from "./i18n"; import { i18n } from "./i18n";
import { del, get, set } from "@/scripts/idb-proxy"; import { del, get, set } from "@/scripts/idb-proxy";
// #v-ifdef VITE_CAPACITOR // #v-ifdef VITE_CAPACITOR
//...
// #v-else // #v-else
import { apiUrl } from "@/config"; import { apiUrl } from "@/config";
// #v-endif // #v-endif
@ -44,6 +45,7 @@ export async function signout() {
const accounts = await getAccounts(); const accounts = await getAccounts();
// #v-ifdef VITE_CAPACITOR // #v-ifdef VITE_CAPACITOR
//...
// #v-else // #v-else
//#region Remove service worker registration //#region Remove service worker registration
try { try {
@ -117,7 +119,32 @@ export async function removeAccount(id: Account["id"]) {
if (accounts.length > 0) await set("accounts", accounts); if (accounts.length > 0) await set("accounts", accounts);
else await del("accounts"); else await del("accounts");
} }
// #v-ifdef VITE_CAPACITOR
function fetchAccount(
token: string,
instanceUrl: string
): Promise<Account & { instanceUrl: string }> {
return new misskey.api.APIClient({ origin: instanceUrl, credential: token })
.request("i")
.then((res) => {
return { ...(res as Account), token, instanceUrl };
})
.catch((res) => {
if (res.error.id === "a8c724b3-6e9c-4b46-b1a8-bc3ed6258370") {
showSuspendedDialog().then(() => {
signout();
});
} else {
alert({
type: "error",
title: i18n.ts.failedToFetchAccountInformation,
text: JSON.stringify(res.error),
});
}
return Promise.reject(res);
});
}
// #v-else
function fetchAccount(token: string): Promise<Account> { function fetchAccount(token: string): Promise<Account> {
return new Promise((done, fail) => { return new Promise((done, fail) => {
// Fetch user // Fetch user
@ -149,32 +176,7 @@ function fetchAccount(token: string): Promise<Account> {
.catch(fail); .catch(fail);
}); });
} }
// #v-endif
function fetchAccount(
token: string,
instanceUrl: string
): Promise<Account & { instanceUrl: string }> {
return new misskey.api.APIClient({ origin: instanceUrl, credential: token })
.request("i")
.then((res) => {
return { ...(res as Account), token, instanceUrl };
})
.catch((res) => {
if (res.error.id === "a8c724b3-6e9c-4b46-b1a8-bc3ed6258370") {
showSuspendedDialog().then(() => {
signout();
});
} else {
alert({
type: "error",
title: i18n.ts.failedToFetchAccountInformation,
text: JSON.stringify(res.error),
});
}
return Promise.reject(res);
});
}
export function updateAccount(accountData: Object) { export function updateAccount(accountData: Object) {
for (const [key, value] of Object.entries(accountData)) { for (const [key, value] of Object.entries(accountData)) {
$i[key] = value; $i[key] = value;
@ -325,11 +327,30 @@ export async function openAccountMenu(
const accountItemPromises = storedAccounts.map( const accountItemPromises = storedAccounts.map(
(a) => (a) =>
new Promise((res) => { new Promise((res) => {
// #v-ifdef VITE_CAPACITOR
const client = new misskey.api.APIClient({
origin: a.instanceUrl,
credential: a.token,
});
client
.request("users/show", {
userIds: [a.id],
})
.then((accounts) => {
const account = accounts.find((x) => x.id === a.id);
if (account == null) return res(null);
client.request("meta").then((meta) => {
res(createItem({ ...account, host: meta.name }));
});
});
// #v-else
accountsPromise.then((accounts) => { accountsPromise.then((accounts) => {
const account = accounts.find((x) => x.id === a.id); const account = accounts.find((x) => x.id === a.id);
if (account == null) return res(null); if (account == null) return res(null);
res(createItem(account)); res(createItem(account));
}); });
// #v-endif
}), }),
); );
@ -357,12 +378,16 @@ export async function openAccountMenu(
showSigninDialog(); showSigninDialog();
}, },
}, },
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
{ {
text: i18n.ts.createAccount, text: i18n.ts.createAccount,
action: () => { action: () => {
createAccount(); createAccount();
}, },
}, },
// #v-endif
], ],
}, },
{ {

View File

@ -1,6 +1,14 @@
<template> <template>
<MkA v-if="url.startsWith('/')" v-user-preview="canonical" class="akbvjaqn" :class="{ isMe }" :to="url" :style="{ background: bgCss }" @click.stop> <MkA v-if="url.startsWith('/')" v-user-preview="canonical" class="akbvjaqn" :class="{ isMe }" :to="url" :style="{ background: bgCss }" @click.stop>
// #v-ifdef VITE_CAPACITOR
<img
:class="$style.icon"
:src="`${$i.instanceUrl}/avatar/@${username}@${host}`"
alt=""
/>
// #v-else
<img class="icon" :src="`/avatar/@${username}@${host}`" alt=""> <img class="icon" :src="`/avatar/@${username}@${host}`" alt="">
// #v-endif
<span class="main"> <span class="main">
<span class="username">@{{ username }}</span> <span class="username">@{{ username }}</span>
<span v-if="(host != localHost) || $store.state.showFullAcct" class="host">@{{ toUnicode(host) }}</span> <span v-if="(host != localHost) || $store.state.showFullAcct" class="host">@{{ toUnicode(host) }}</span>

View File

@ -34,6 +34,9 @@
</a> </a>
<button v-else-if="item.type === 'user' && !items.hidden" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <button v-else-if="item.type === 'user' && !items.hidden" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
<MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/> <MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/>
// #v-ifdef VITE_CAPACITOR
[{{ item.user.host }}]
// #v-endif
<span v-if="item.indicate" class="indicator"><i class="ph-circle ph-fill"></i></span> <span v-if="item.indicate" class="indicator"><i class="ph-circle ph-fill"></i></span>
</button> </button>
<span v-else-if="item.type === 'switch'" :tabindex="i" class="item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <span v-else-if="item.type === 'switch'" :tabindex="i" class="item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">

View File

@ -1,4 +1,56 @@
<template> <template>
// #v-ifdef VITE_CAPACITOR
<form class="eppvobhk" :class="{ signing }" @submit.prevent="onSubmit">
<div class="normal-signin">
{{ i18n.ts.ririca.instance }}
<MkSelect v-model="instanceUrl" large :model-value="instances[0]?.url">
<option value="other">
{{ i18n.ts.ririca.selectInstanceYourself }}
</option>
<option
v-for="(instance, i) in instances"
:key="instance.url"
:value="instance.url"
:selected="i === 0"
>
{{ instance.name }}
</option>
</MkSelect>
<template v-if="instanceUrl === 'other'">
URL
<MkInput
v-model="instanceUrlOther"
:spellcheck="false"
autofocus
required
/>
</template>
{{ i18n.ts.ririca.accessToken }}
<MkInput
v-model="token"
:spellcheck="false"
autofocus
required
data-cy-signin-username
></MkInput>
<MkButton
class="_formBlock"
type="submit"
primary
:disabled="signing"
style="margin: 0 auto"
>
{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}
</MkButton>
</div>
<div style="display: flex; justify-content: center"></div>
<a
href="https://misskey.io/notes/99l9jqqun2"
target="_blank"
style="color: var(--link); text-align: center"
>{{ i18n.ts.ririca.howToCreateToken }}</a>
</form>
// #v-else
<form class="eppvobhk _monolithic_" :class="{ signing, totpLogin }" @submit.prevent="onSubmit"> <form class="eppvobhk _monolithic_" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
<div class="auth _section _formRoot"> <div class="auth _section _formRoot">
<div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div> <div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div>
@ -46,9 +98,11 @@
<a v-if="meta && meta.enableDiscordIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/discord`"><i class="ph-discord-logo ph-bold ph-lg" style="margin-right: 4px;"></i>{{ i18n.t('signinWith', { x: 'Discord' }) }}</a> <a v-if="meta && meta.enableDiscordIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/discord`"><i class="ph-discord-logo ph-bold ph-lg" style="margin-right: 4px;"></i>{{ i18n.t('signinWith', { x: 'Discord' }) }}</a>
</div> </div>
</form> </form>
// #v-endif
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import MkSelect from "@/components/form/select.vue";
import { defineAsyncComponent } from 'vue'; import { defineAsyncComponent } from 'vue';
import { toUnicode } from 'punycode/'; import { toUnicode } from 'punycode/';
import { showSuspendedDialog } from '../scripts/show-suspended-dialog'; import { showSuspendedDialog } from '../scripts/show-suspended-dialog';
@ -67,14 +121,23 @@ let user = $ref(null);
let username = $ref(''); let username = $ref('');
let password = $ref(''); let password = $ref('');
let token = $ref(''); let token = $ref('');
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
let host = $ref(toUnicode(configHost)); let host = $ref(toUnicode(configHost));
let totpLogin = $ref(false); let totpLogin = $ref(false);
// #v-endif
let credential = $ref(null); let credential = $ref(null);
let challengeData = $ref(null); let challengeData = $ref(null);
let queryingKey = $ref(false); let queryingKey = $ref(false);
let hCaptchaResponse = $ref(null); let hCaptchaResponse = $ref(null);
let reCaptchaResponse = $ref(null); let reCaptchaResponse = $ref(null);
// #v-ifdef VITE_CAPACITOR
const instanceUrl = $ref("");
const instanceUrlOther = $ref("");
// #v-endif
const meta = $computed(() => instance); const meta = $computed(() => instance);
const emit = defineEmits<{ const emit = defineEmits<{
@ -111,10 +174,24 @@ function onUsernameChange() {
function onLogin(res) { function onLogin(res) {
if (props.autoSet) { if (props.autoSet) {
// #v-ifdef VITE_CAPACITOR
return login(res.i, res.instance);
// #v-else
return login(res.i); return login(res.i);
// #v-endif
} }
} }
// #v-ifdef VITE_CAPACITOR
const instanceUrlResult = $computed(() => {
if (instanceUrl === "other") {
// https://replace
// new URL.origin
return new URL("https://" + instanceUrlOther.replace("https://", ""))
.origin;
}
return "https://" + instanceUrl;
});
// #v-endif
function queryKey() { function queryKey() {
queryingKey = true; queryingKey = true;
return navigator.credentials.get({ return navigator.credentials.get({
@ -145,8 +222,13 @@ function queryKey() {
'g-recaptcha-response': reCaptchaResponse, 'g-recaptcha-response': reCaptchaResponse,
}); });
}).then(res => { }).then(res => {
// #v-ifdef VITE_CAPACITOR
emit("login", { ...res, instance: instanceUrl });
return onLogin({ ...res, instance: instanceUrl });
// #v-else
emit('login', res); emit('login', res);
return onLogin(res); return onLogin(res);
// #v-endif
}).catch(err => { }).catch(err => {
if (err === null) return; if (err === null) return;
os.alert({ os.alert({
@ -160,6 +242,11 @@ function queryKey() {
function onSubmit() { function onSubmit() {
signing = true; signing = true;
console.log('submit'); console.log('submit');
// #v-ifdef VITE_CAPACITOR
if (!token.valueOf()) {
login(token, instanceUrlResult);
signing = false;
// #v-else
if (!totpLogin && user && user.twoFactorEnabled) { if (!totpLogin && user && user.twoFactorEnabled) {
if (window.PublicKeyCredential && user.securityKeys) { if (window.PublicKeyCredential && user.securityKeys) {
os.api('signin', { os.api('signin', {
@ -188,6 +275,7 @@ function onSubmit() {
emit('login', res); emit('login', res);
onLogin(res); onLogin(res);
}).catch(loginFailed); }).catch(loginFailed);
// #v-endif
} }
} }
@ -240,6 +328,15 @@ function resetPassword() {
os.popup(defineAsyncComponent(() => import('@/components/MkForgotPassword.vue')), {}, { os.popup(defineAsyncComponent(() => import('@/components/MkForgotPassword.vue')), {}, {
}, 'closed'); }, 'closed');
} }
// #v-ifdef VITE_CAPACITOR
let instances = $ref([]);
fetch("https://api.calckey.org/instances.json").then((res) => {
res.json().then((data) => {
instances = data.instancesInfos;
});
});
// #v-endif
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,6 +1,12 @@
<template> <template>
<img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/> <img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/>
<img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" :title="alt" decoding="async"/> <img v-else-if="char && !useOsNativeEmojis" class="mk-emoji"
// #v-ifdef VITE_CAPACITOR
:src="char2path(char)"
// #v-else
:src="url"
// #v-endif
:alt="alt" :title="alt" decoding="async"/>
<span v-else-if="char && useOsNativeEmojis">{{ char }}</span> <span v-else-if="char && useOsNativeEmojis">{{ char }}</span>
<span v-else>{{ emoji }}</span> <span v-else>{{ emoji }}</span>
</template> </template>
@ -12,6 +18,7 @@ import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import { char2filePath } from '@/scripts/twemoji-base'; import { char2filePath } from '@/scripts/twemoji-base';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { instance } from '@/instance'; import { instance } from '@/instance';
import { url as instanceUrl } from "@/config";
const props = defineProps<{ const props = defineProps<{
emoji: string; emoji: string;
@ -28,7 +35,10 @@ const ce = computed(() => props.customEmojis ?? instance.emojis ?? []);
const customEmoji = computed(() => isCustom.value ? ce.value.find(x => x.name === props.emoji.substr(1, props.emoji.length - 2)) : null); const customEmoji = computed(() => isCustom.value ? ce.value.find(x => x.name === props.emoji.substr(1, props.emoji.length - 2)) : null);
const url = computed(() => { const url = computed(() => {
if (char.value) { if (char.value) {
// #v-ifdef VITE_CAPACITOR
// #v-else
return char2filePath(char.value); return char2filePath(char.value);
// #v-endif
} else { } else {
return defaultStore.state.disableShowingAnimatedImages return defaultStore.state.disableShowingAnimatedImages
? getStaticImageUrl(customEmoji.value.url) ? getStaticImageUrl(customEmoji.value.url)

View File

@ -1,11 +1,20 @@
import { $i } from "@/account";
// #v-ifdef VITE_CAPACITOR
const address = $i ? new URL($i.instanceUrl) : null;
// #v-else
const address = new URL(location.href); const address = new URL(location.href);
// #v-endif
const siteName = ( const siteName = (
document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement
)?.content; )?.content;
export const host = address.host; export const host = address?.host;
export const hostname = address.hostname; export const hostname = address?.hostname;
// #v-ifdef VITE_CAPACITOR
export const url = $i?.instanceUrl;
// #v-else
export const url = address.origin; export const url = address.origin;
// #v-endif
export const apiUrl = `${url}/api`; export const apiUrl = `${url}/api`;
export const wsUrl = `${url export const wsUrl = `${url
.replace("http://", "ws://") .replace("http://", "ws://")

View File

@ -50,10 +50,11 @@ import { reloadChannel } from "@/scripts/unison-reload";
import { reactionPicker } from "@/scripts/reaction-picker"; import { reactionPicker } from "@/scripts/reaction-picker";
import { getUrlWithoutLoginId } from "@/scripts/login-id"; import { getUrlWithoutLoginId } from "@/scripts/login-id";
import { getAccountFromId } from "@/scripts/get-account-from-id"; import { getAccountFromId } from "@/scripts/get-account-from-id";
import { Device } from "@capacitor/device"; import { Device, DeviceInfo } from "@capacitor/device";
import { App } from "@capacitor/app"; import { App } from "@capacitor/app";
import lightTheme from "@/themes/_light.json5"; import lightThemeDefault from "@/themes/_light.json5";
export let storedDeviceInfo: Object; import OneSignal from "onesignal-cordova-plugin";
export let storedDeviceInfo: DeviceInfo;
// #v-ifdef VITE_CAPACITOR // #v-ifdef VITE_CAPACITOR
const onesignal_app_id = "efe09597-0778-4156-97b7-0bf8f52c21a7"; const onesignal_app_id = "efe09597-0778-4156-97b7-0bf8f52c21a7";
// #v-endif // #v-endif
@ -122,7 +123,7 @@ const onesignal_app_id = "efe09597-0778-4156-97b7-0bf8f52c21a7";
let isMobileApp = false; let isMobileApp = false;
// #v-ifdef VITE_CAPACITOR // #v-ifdef VITE_CAPACITOR
isMobileApp = false; isMobileApp = true;
// #v-endif // #v-endif
// If mobile, insert the viewport meta tag // If mobile, insert the viewport meta tag
@ -214,11 +215,13 @@ const onesignal_app_id = "efe09597-0778-4156-97b7-0bf8f52c21a7";
if (_DEV_) { if (_DEV_) {
console.log("not signed in"); console.log("not signed in");
} }
applyTheme(lightTheme); applyTheme(lightThemeDefault);
} }
} }
//#endregion //#endregion
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
const fetchInstanceMetaPromise = fetchInstance(); const fetchInstanceMetaPromise = fetchInstance();
fetchInstanceMetaPromise.then(() => { fetchInstanceMetaPromise.then(() => {
@ -227,7 +230,7 @@ const onesignal_app_id = "efe09597-0778-4156-97b7-0bf8f52c21a7";
// Init service worker // Init service worker
initializeSw(); initializeSw();
}); });
// #v-endif
const app = createApp( const app = createApp(
window.location.search === "?zen" window.location.search === "?zen"
? defineAsyncComponent(() => import("@/ui/zen.vue")) ? defineAsyncComponent(() => import("@/ui/zen.vue"))
@ -287,7 +290,11 @@ const onesignal_app_id = "efe09597-0778-4156-97b7-0bf8f52c21a7";
window.onerror = null; window.onerror = null;
window.onunhandledrejection = null; window.onunhandledrejection = null;
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
reactionPicker.init(); reactionPicker.init();
// #v-endif
if (splash) { if (splash) {
splash.style.opacity = "0"; splash.style.opacity = "0";
@ -325,6 +332,45 @@ const onesignal_app_id = "efe09597-0778-4156-97b7-0bf8f52c21a7";
} }
} }
// #v-ifdef VITE_CAPACITOR
App.addListener("backButton", (canGoBack) => {
if (canGoBack) {
history.back();
} else {
App.exitApp();
}
});
})();
async function afterLoginSetup() {
if (!$i) return;
const hotkeys = {
d: (): void => {
defaultStore.set("darkMode", !defaultStore.state.darkMode);
},
s: search,
["p|n"]: post,
};
//shortcut
document.addEventListener("keydown", makeHotkey(hotkeys));
reactionPicker.init();
const fetchInstanceMetaPromise = fetchInstance();
fetchInstanceMetaPromise.then(() => {
localStorage.setItem("v", instance.version);
// Init service worker
initializeSw();
});
applyTheme(
defaultStore.reactiveState.darkMode.value ?
ColdDeviceStorage.get("darkTheme") :
ColdDeviceStorage.get("lightTheme")
)
// #v-endif
// NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため) // NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため)
watch( watch(
defaultStore.reactiveState.darkMode, defaultStore.reactiveState.darkMode,
@ -365,6 +411,9 @@ const onesignal_app_id = "efe09597-0778-4156-97b7-0bf8f52c21a7";
}); });
//#endregion //#endregion
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
fetchInstanceMetaPromise.then(() => { fetchInstanceMetaPromise.then(() => {
if (defaultStore.state.themeInitial) { if (defaultStore.state.themeInitial) {
if (instance.defaultLightTheme != null) if (instance.defaultLightTheme != null)
@ -380,6 +429,7 @@ const onesignal_app_id = "efe09597-0778-4156-97b7-0bf8f52c21a7";
defaultStore.set("themeInitial", false); defaultStore.set("themeInitial", false);
} }
}); });
// #v-endif
watch( watch(
defaultStore.reactiveState.useBlurEffectForModal, defaultStore.reactiveState.useBlurEffectForModal,
@ -436,12 +486,16 @@ const onesignal_app_id = "efe09597-0778-4156-97b7-0bf8f52c21a7";
}); });
} }
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
const hotkeys = { const hotkeys = {
d: (): void => { d: (): void => {
defaultStore.set("darkMode", !defaultStore.state.darkMode); defaultStore.set("darkMode", !defaultStore.state.darkMode);
}, },
s: search, s: search,
}; };
// #v-endif
if ($i) { if ($i) {
// only add post shortcuts if logged in // only add post shortcuts if logged in
@ -544,6 +598,45 @@ const onesignal_app_id = "efe09597-0778-4156-97b7-0bf8f52c21a7";
}); });
} }
// #v-ifdef VITE_CAPACITOR
if (storedDeviceInfo.platform === "web") return;
OneSignal.setAppId(onesignal_app_id);
const deviceId = await Device.getId();
OneSignal.setExternalUserId(deviceId.uuid);
/*const res = await fetch(import.meta.env.VITE_NOTIFICATION_TOKEN_ENDPOINT, {
method: "POST", // *GET, POST, PUT, DELETE, etc.
mode: "cors", // no-cors, *cors, same-origin
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "same-origin", // include, *same-origin, omit
headers: {
"Content-Type": "application/json",
},
redirect: "follow", // manual, *follow, error
referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify({
misskey_token: $i.token,
device_id: deviceId.uuid,
instance_url: $i.instanceUrl,
}),
}).catch((err) => {
console.error(err);
// throw err
});
console.info(res);*/
OneSignal.setNotificationOpenedHandler(function (jsonData) {
console.log(`notificationOpenedCallback: ${JSON.stringify(jsonData)}`);
});
// Prompts the user for notification permissions.
// * Since this shows a generic native prompt, we recommend instead using an In-App Message to prompt for notification permission (See step 7) to better communicate to your users what notifications they will get.
OneSignal.promptForPushNotificationsWithUserResponse(function (accepted) {
console.log(`User accepted notifications: ${accepted}`);
});
// #v-endif
// shortcut // shortcut
document.addEventListener("keydown", makeHotkey(hotkeys)); document.addEventListener("keydown", makeHotkey(hotkeys));
// #v-ifdef VITE_CAPACITOR
}
// #v-else
})(); })();
// #v-endif

View File

@ -15,7 +15,11 @@ import { $i } from "@/account";
export const pendingApiRequestsCount = ref(0); export const pendingApiRequestsCount = ref(0);
const apiClient = new Misskey.api.APIClient({ const apiClient = new Misskey.api.APIClient({
// #v-ifdef VITE_CAPACITOR
origin: $i?.instanceUrl || window.location.origin,
// #v-else
origin: url, origin: url,
// #v-endif
}); });
export const api = (( export const api = ((

View File

@ -78,6 +78,9 @@ import { definePageMetadata } from '@/scripts/page-metadata';
import { deviceKind } from '@/scripts/device-kind'; import { deviceKind } from '@/scripts/device-kind';
import 'swiper/scss'; import 'swiper/scss';
import 'swiper/scss/virtual'; import 'swiper/scss/virtual';
// #v-ifdef VITE_CAPACITOR
import { Camera } from "@capacitor/camera";
// #v-endif
if (defaultStore.reactiveState.tutorial.value !== -1) { if (defaultStore.reactiveState.tutorial.value !== -1) {
os.popup(XTutorial, {}, {}, 'closed'); os.popup(XTutorial, {}, {}, 'closed');
@ -339,6 +342,13 @@ onMounted(() => {
syncSlide(timelines.indexOf(swiperRef.activeIndex)); syncSlide(timelines.indexOf(swiperRef.activeIndex));
}); });
// #v-ifdef VITE_CAPACITOR
const permissionState = await Camera.checkPermissions();
if (!permissionState.camera) {
Camera.requestPermissions({ permissions: ["photos", "camera"] });
}
// #v-endif
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,8 +1,16 @@
<template> <template>
// #v-ifdef VITE_CAPACITOR
<div class="rsqzvsbo">
// #v-else
<div v-if="meta" class="rsqzvsbo"> <div v-if="meta" class="rsqzvsbo">
// #v-endif
<div class="top"> <div class="top">
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
<MkFeaturedPhotos class="bg"/> <MkFeaturedPhotos class="bg"/>
<XTimeline class="tl"/> <XTimeline class="tl"/>
// #v-endif
<div class="shape1"></div> <div class="shape1"></div>
<div class="shape2"></div> <div class="shape2"></div>
<img src="/client-assets/misskey.svg" class="misskey"/> <img src="/client-assets/misskey.svg" class="misskey"/>
@ -20,20 +28,46 @@
<MkEmoji :normal="true" :no-style="true" emoji="🍮"/> <MkEmoji :normal="true" :no-style="true" emoji="🍮"/>
</div> </div>
<div class="main"> <div class="main">
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
<img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/> <img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
// #v-endif
<button class="_button _acrylic menu" @click="showMenu"><i class="ph-dots-three-outline ph-bold ph-lg"></i></button> <button class="_button _acrylic menu" @click="showMenu"><i class="ph-dots-three-outline ph-bold ph-lg"></i></button>
<div class="fg"> <div class="fg">
<h1> <h1>
// #v-ifdef VITE_CAPACITOR
<span class="text">Calckey Mobile</span>
// #v-else
<img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"> <img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl">
<span v-else class="text">{{ instanceName }}</span> <span v-else class="text">{{ instanceName }}</span>
// #v-endif
</h1> </h1>
<div class="about"> <div class="about">
// #v-ifdef VITE_CAPACITOR
<div class="desc" v-html="i18n.ts.headlineMisskey"></div>
// #v-else
<div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div> <div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div>
// #v-endif
</div> </div>
<div class="action"> <div class="action">
// #v-ifdef VITE_CAPACITOR
<div>
<input id="term" v-model="isTerm" type="checkbox" /><label
for="term"
>
Agree to Privacy Policy and Terms of Use</label
><br />
<a href="https://riinswork.space/missRirica/privacy/">
Read Privacy Policy and Terms of Use
</a>
</div>
<MkButton inline rounded data-cy-signin :disabled="!isTerm" @click="signin()">{{ i18n.ts.login }}</MkButton>
// #v-else
<MkButton inline rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.signup }}</MkButton> <MkButton inline rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.signup }}</MkButton>
<MkButton inline rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton> <MkButton inline rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton>
<MkButton inline rounded style="margin-left: 12px; margin-top: 12px;" onclick="window.location.href='/explore'">Explore</MkButton> <MkButton inline rounded style="margin-left: 12px; margin-top: 12px;" onclick="window.location.href='/explore'">Explore</MkButton>
// #v-endif
</div> </div>
</div> </div>
</div> </div>
@ -60,16 +94,23 @@ import MkButton from '@/components/MkButton.vue';
import XNote from '@/components/MkNote.vue'; import XNote from '@/components/MkNote.vue';
import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue'; import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
import { host, instanceName } from '@/config'; import { host, instanceName } from '@/config';
import { ref, watch } from "vue";
import * as os from '@/os'; import * as os from '@/os';
import number from '@/filters/number';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { langs as _langs } from "@/config";
const langs = ref(_langs);
const lang = ref(localStorage.getItem("lang"));
let isTerm = $ref();
let meta = $ref(); let meta = $ref();
let stats = $ref(); let stats = $ref();
let tags = $ref(); let tags = $ref();
let onlineUsersCount = $ref(); let onlineUsersCount = $ref();
let instances = $ref(); let instances = $ref();
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
os.api('meta', { detail: true }).then(_meta => { os.api('meta', { detail: true }).then(_meta => {
meta = _meta; meta = _meta;
}); });
@ -95,19 +136,22 @@ os.api('federation/instances', {
}).then(_instances => { }).then(_instances => {
instances = _instances; instances = _instances;
}); });
// #v-endif
function signin() { function signin() {
os.popup(XSigninDialog, { os.popup(XSigninDialog, {
autoSet: true, autoSet: true,
}, {}, 'closed'); }, {}, 'closed');
} }
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
function signup() { function signup() {
os.popup(XSignupDialog, { os.popup(XSignupDialog, {
autoSet: true, autoSet: true,
}, {}, 'closed'); }, {}, 'closed');
} }
// #v-endif
function showMenu(ev) { function showMenu(ev) {
os.popupMenu([{ os.popupMenu([{
text: i18n.ts.instanceInfo, text: i18n.ts.instanceInfo,
@ -123,6 +167,34 @@ function showMenu(ev) {
}, },
}], ev.currentTarget ?? ev.target); }], ev.currentTarget ?? ev.target);
} }
// #v-ifdef VITE_CAPACITOR
watch(lang, () => {
localStorage.setItem("lang", lang.value as string);
localStorage.removeItem("locale");
});
watch([lang], async () => {
await reloadAsk();
});
async function reloadAsk() {
const { canceled } = await os.confirm({
type: "info",
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
unisonReload();
}
function unisonReload(path?: string) {
if (path !== undefined) {
location.href = path;
} else {
location.reload();
}
}
// #v-endif
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -73,7 +73,11 @@ export default defineComponent({
MkButton, MkButton,
XNote, XNote,
MkFeaturedPhotos, MkFeaturedPhotos,
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
XTimeline, XTimeline,
// #v-endif
}, },
data() { data() {

View File

@ -1,14 +1,21 @@
<template> <template>
// #v-ifdef VITE_CAPACITOR
<XEntrance />
// #v-else
<div v-if="meta"> <div v-if="meta">
<XSetup v-if="meta.requireSetup"/> <XSetup v-if="meta.requireSetup"/>
<XEntrance v-else/> <XEntrance v-else/>
</div> </div>
// #v-endif
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import XEntrance from './welcome.entrance.a.vue';
// #v-ifdef VITE_CAPACITOR
//...
// #v-else
import { computed } from 'vue'; import { computed } from 'vue';
import XSetup from './welcome.setup.vue'; import XSetup from './welcome.setup.vue';
import XEntrance from './welcome.entrance.a.vue';
import { instanceName } from '@/config'; import { instanceName } from '@/config';
import * as os from '@/os'; import * as os from '@/os';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
@ -27,4 +34,5 @@ definePageMetadata(computed(() => ({
title: instanceName, title: instanceName,
icon: null, icon: null,
}))); })));
// #v-endif
</script> </script>

View File

@ -15,6 +15,9 @@ import lightTheme from "@/themes/_light.json5";
import darkTheme from "@/themes/_dark.json5"; import darkTheme from "@/themes/_dark.json5";
import { deepClone } from "./clone"; import { deepClone } from "./clone";
import { StatusBar, Style } from "@capacitor/status-bar";
import { storedDeviceInfo } from "@/init";
export const themeProps = Object.keys(lightTheme.props).filter( export const themeProps = Object.keys(lightTheme.props).filter(
(key) => !key.startsWith("X"), (key) => !key.startsWith("X"),
); );
@ -140,6 +143,13 @@ function compile(theme: Theme): Record<string, string> {
return color.saturate(arg); return color.saturate(arg);
} }
} }
// #v-ifdef VITE_CAPACITOR
if (storedDeviceInfo.platform === "ios") {
StatusBar.setStyle({
style: theme.base === "dark" ? Style.Dark : Style.Light,
});
}
// #v-endif
// other case // other case
return tinycolor(val); return tinycolor(val);

View File

@ -5,7 +5,11 @@ import { url } from "@/config";
export const stream = markRaw( export const stream = markRaw(
new Misskey.Stream( new Misskey.Stream(
// #v-ifdef VITE_CAPACITOR
$i? $i.instanceUrl : "localhost",
// #v-else
url, url,
// #v-endif
$i $i
? { ? {
token: $i.token, token: $i.token,

View File

@ -81,7 +81,6 @@ const isTimelineAvailable = !instance.disableLocalTimeline || !instance.disableR
let showMenu = $ref(false); let showMenu = $ref(false);
let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD); let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD);
let narrow = $ref(window.innerWidth < 1280); let narrow = $ref(window.innerWidth < 1280);
let meta = $ref();
const keymap = $computed(() => { const keymap = $computed(() => {
return { return {
@ -95,10 +94,6 @@ const keymap = $computed(() => {
const root = $computed(() => mainRouter.currentRoute.value.name === 'index'); const root = $computed(() => mainRouter.currentRoute.value.name === 'index');
os.api('meta', { detail: true }).then(res => {
meta = res;
});
function signin() { function signin() {
os.popup(XSigninDialog, { os.popup(XSigninDialog, {
autoSet: true, autoSet: true,

View File

@ -598,6 +598,9 @@ importers:
'@capacitor/app': '@capacitor/app':
specifier: ^4.1.1 specifier: ^4.1.1
version: 4.1.1(@capacitor/core@4.7.3) version: 4.1.1(@capacitor/core@4.7.3)
'@capacitor/camera':
specifier: ^4.1.4
version: 4.1.4(@capacitor/core@4.7.3)
'@capacitor/core': '@capacitor/core':
specifier: ^4.7.3 specifier: ^4.7.3
version: 4.7.3 version: 4.7.3
@ -1119,6 +1122,14 @@ packages:
'@capacitor/core': 4.7.3 '@capacitor/core': 4.7.3
dev: false dev: false
/@capacitor/camera@4.1.4(@capacitor/core@4.7.3):
resolution: {integrity: sha512-7f4n7PlnstYsdGyxc0Kc4hIyS6csFLOjXvffm7pJsuy9pSo+kfdOPdYgGg360QwbUAmA+Yv+J1ZW1eFIwgvWrQ==}
peerDependencies:
'@capacitor/core': ^4.0.0
dependencies:
'@capacitor/core': 4.7.3
dev: false
/@capacitor/cli@4.7.3: /@capacitor/cli@4.7.3:
resolution: {integrity: sha512-/9Vjl0A91UjRA0b8a5YCqHfHcM50vQ3Fh0typ3ScwYZhg3shkFtB03e8liAD/u0rhr/MerN8VGgN75QNEXN0Lg==} resolution: {integrity: sha512-/9Vjl0A91UjRA0b8a5YCqHfHcM50vQ3Fh0typ3ScwYZhg3shkFtB03e8liAD/u0rhr/MerN8VGgN75QNEXN0Lg==}
engines: {node: '>=12.4.0'} engines: {node: '>=12.4.0'}