mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: replace wasm with axios (#5856)
* fix: replace wasm with axios * fix: login redirect * fix: flag emoji on windows
This commit is contained in:
parent
04556252e1
commit
cb60488bbe
@ -54,10 +54,7 @@ http {
|
|||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
expires 30d;
|
expires 30d;
|
||||||
access_log off;
|
access_log off;
|
||||||
location ~* \.wasm$ {
|
|
||||||
types { application/wasm wasm; }
|
|
||||||
default_type application/wasm;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location /appflowy.svg {
|
location /appflowy.svg {
|
||||||
|
@ -68,14 +68,25 @@ const createServer = async (req: Request) => {
|
|||||||
|
|
||||||
logger.info(`Request URL: ${hostname}${reqUrl.pathname}`);
|
logger.info(`Request URL: ${hostname}${reqUrl.pathname}`);
|
||||||
|
|
||||||
if (reqUrl.pathname === '/after-payment') {
|
if (['/after-payment', '/login'].includes(reqUrl.pathname)) {
|
||||||
timer();
|
timer();
|
||||||
const htmlData = fs.readFileSync(indexPath, 'utf8');
|
const htmlData = fs.readFileSync(indexPath, 'utf8');
|
||||||
const $ = load(htmlData);
|
const $ = load(htmlData);
|
||||||
|
|
||||||
$('title').text('Payment Success | AppFlowy');
|
let title, description;
|
||||||
$('link[rel="icon"]').attr('href', '/appflowy.svg');
|
|
||||||
setOrUpdateMetaTag($, 'meta[name="description"]', 'name', 'Payment success on AppFlowy');
|
if (reqUrl.pathname === '/after-payment') {
|
||||||
|
title = 'Payment Success | AppFlowy';
|
||||||
|
description = 'Payment success on AppFlowy';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reqUrl.pathname === '/login') {
|
||||||
|
title = 'Login | AppFlowy';
|
||||||
|
description = 'Login to AppFlowy';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title) $('title').text(title);
|
||||||
|
if (description) setOrUpdateMetaTag($, 'meta[name="description"]', 'name', description);
|
||||||
|
|
||||||
return new Response($.html(), {
|
return new Response($.html(), {
|
||||||
headers: { 'Content-Type': 'text/html' },
|
headers: { 'Content-Type': 'text/html' },
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
"coverage": "pnpm run test:unit && pnpm run test:components"
|
"coverage": "pnpm run test:unit && pnpm run test:components"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@appflowyinc/client-api-wasm": "0.1.4",
|
|
||||||
"@atlaskit/primitives": "^5.5.3",
|
"@atlaskit/primitives": "^5.5.3",
|
||||||
"@emoji-mart/data": "^1.1.2",
|
"@emoji-mart/data": "^1.1.2",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
|
@ -5,9 +5,6 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@appflowyinc/client-api-wasm':
|
|
||||||
specifier: 0.1.4
|
|
||||||
version: 0.1.4
|
|
||||||
'@atlaskit/primitives':
|
'@atlaskit/primitives':
|
||||||
specifier: ^5.5.3
|
specifier: ^5.5.3
|
||||||
version: 5.7.0(@types/react@18.2.66)(react@18.2.0)
|
version: 5.7.0(@types/react@18.2.66)(react@18.2.0)
|
||||||
@ -454,10 +451,6 @@ packages:
|
|||||||
'@jridgewell/gen-mapping': 0.3.5
|
'@jridgewell/gen-mapping': 0.3.5
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
|
|
||||||
/@appflowyinc/client-api-wasm@0.1.4:
|
|
||||||
resolution: {integrity: sha512-3uBpy3n+aIG0fapPAroMfL8JLdAPtqPAkpV+LOxlRnMW4Au2JQcW8TW0P3K1YAe16tDZ62ZIZPoG6Bi40RDRoQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@atlaskit/analytics-next-stable-react-context@1.0.1(react@18.2.0):
|
/@atlaskit/analytics-next-stable-react-context@1.0.1(react@18.2.0):
|
||||||
resolution: {integrity: sha512-iO6+hIp09dF4iAZQarVz3vKY1kM5Ij5CExYcK9jgc2q+OH8nv8n+BPFeJTdzGOGopmbUZn5Opj9pYQvge1Gr4Q==}
|
resolution: {integrity: sha512-iO6+hIp09dF4iAZQarVz3vKY1kM5Ij5CExYcK9jgc2q+OH8nv8n+BPFeJTdzGOGopmbUZn5Opj9pYQvge1Gr4Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { expect } from '@jest/globals';
|
import { expect } from '@jest/globals';
|
||||||
import { fetchPublishView, fetchPublishViewMeta, fetchViewInfo } from '../fetch';
|
import { fetchPublishView, fetchPublishViewMeta, fetchViewInfo } from '../fetch';
|
||||||
import { APIService } from '@/application/services/js-services/wasm';
|
import { APIService } from '@/application/services/js-services/http';
|
||||||
|
|
||||||
jest.mock('@/application/services/js-services/wasm', () => {
|
jest.mock('@/application/services/js-services/http', () => {
|
||||||
return {
|
return {
|
||||||
APIService: {
|
APIService: {
|
||||||
getPublishView: jest.fn(),
|
getPublishView: jest.fn(),
|
||||||
|
@ -5,7 +5,7 @@ import { fetchViewInfo } from '@/application/services/js-services/fetch';
|
|||||||
import { expect, jest } from '@jest/globals';
|
import { expect, jest } from '@jest/globals';
|
||||||
import { getPublishView, getPublishViewMeta } from '@/application/services/js-services/cache';
|
import { getPublishView, getPublishViewMeta } from '@/application/services/js-services/cache';
|
||||||
|
|
||||||
jest.mock('@/application/services/js-services/wasm/client_api', () => {
|
jest.mock('@/application/services/js-services/http/http_api', () => {
|
||||||
return {
|
return {
|
||||||
initAPIService: jest.fn(),
|
initAPIService: jest.fn(),
|
||||||
};
|
};
|
||||||
|
@ -114,7 +114,7 @@ export async function getPublishViewMeta<
|
|||||||
|
|
||||||
export async function getPublishView<
|
export async function getPublishView<
|
||||||
T extends {
|
T extends {
|
||||||
data: number[];
|
data: Uint8Array;
|
||||||
rows?: Record<RowId, number[]>;
|
rows?: Record<RowId, number[]>;
|
||||||
visibleViewIds?: ViewId[];
|
visibleViewIds?: ViewId[];
|
||||||
relations?: Record<DatabaseId, ViewId>;
|
relations?: Record<DatabaseId, ViewId>;
|
||||||
@ -219,7 +219,7 @@ export async function revalidatePublishViewMeta<
|
|||||||
|
|
||||||
export async function revalidatePublishView<
|
export async function revalidatePublishView<
|
||||||
T extends {
|
T extends {
|
||||||
data: number[];
|
data: Uint8Array;
|
||||||
rows?: Record<RowId, number[]>;
|
rows?: Record<RowId, number[]>;
|
||||||
visibleViewIds?: ViewId[];
|
visibleViewIds?: ViewId[];
|
||||||
relations?: Record<DatabaseId, ViewId>;
|
relations?: Record<DatabaseId, ViewId>;
|
||||||
@ -260,10 +260,9 @@ export async function revalidatePublishView<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('====', rows);
|
console.log('====', data);
|
||||||
const state = new Uint8Array(data);
|
|
||||||
|
|
||||||
applyYDoc(collab, state);
|
applyYDoc(collab, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteViewMeta(name: string) {
|
export async function deleteViewMeta(name: string) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { APIService } from '@/application/services/js-services/wasm';
|
import { APIService } from '@/application/services/js-services/http';
|
||||||
|
|
||||||
const pendingRequests = new Map();
|
const pendingRequests = new Map();
|
||||||
|
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
import { refreshToken as refreshSessionToken } from '@/application/session/token';
|
||||||
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
|
let axiosInstance: AxiosInstance | null = null;
|
||||||
|
|
||||||
|
export function initGrantService(baseURL: string) {
|
||||||
|
if (axiosInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
axiosInstance = axios.create({
|
||||||
|
baseURL,
|
||||||
|
});
|
||||||
|
|
||||||
|
axiosInstance.interceptors.request.use((config) => {
|
||||||
|
Object.assign(config.headers, {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function refreshToken(refresh_token: string) {
|
||||||
|
const response = await axiosInstance?.post<{
|
||||||
|
access_token: string;
|
||||||
|
expires_at: number;
|
||||||
|
refresh_token: string;
|
||||||
|
}>('/token?grant_type=refresh_token', {
|
||||||
|
refresh_token,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newToken = response?.data;
|
||||||
|
|
||||||
|
if (newToken) {
|
||||||
|
refreshSessionToken(JSON.stringify(newToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
return newToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signInWithMagicLink(email: string, authUrl: string) {
|
||||||
|
const res = await axiosInstance?.post(
|
||||||
|
'/magiclink',
|
||||||
|
{
|
||||||
|
code_challenge: '',
|
||||||
|
code_challenge_method: '',
|
||||||
|
data: {},
|
||||||
|
email,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Redirect_to: authUrl,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function settings() {
|
||||||
|
const res = await axiosInstance?.get('/settings');
|
||||||
|
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function signInGoogle(authUrl: string) {
|
||||||
|
const provider = 'google';
|
||||||
|
const redirectTo = encodeURIComponent(authUrl);
|
||||||
|
const accessType = 'offline';
|
||||||
|
const prompt = 'consent';
|
||||||
|
const baseURL = axiosInstance?.defaults.baseURL;
|
||||||
|
const url = `${baseURL}/authorize?provider=${provider}&redirect_to=${redirectTo}&access_type=${accessType}&prompt=${prompt}`;
|
||||||
|
|
||||||
|
window.open(url, '_current');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function signInGithub(authUrl: string) {
|
||||||
|
const provider = 'github';
|
||||||
|
const redirectTo = encodeURIComponent(authUrl);
|
||||||
|
const baseURL = axiosInstance?.defaults.baseURL;
|
||||||
|
const url = `${baseURL}/authorize?provider=${provider}&redirect_to=${redirectTo}`;
|
||||||
|
|
||||||
|
window.open(url, '_current');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function signInDiscord(authUrl: string) {
|
||||||
|
const provider = 'discord';
|
||||||
|
const redirectTo = encodeURIComponent(authUrl);
|
||||||
|
const baseURL = axiosInstance?.defaults.baseURL;
|
||||||
|
const url = `${baseURL}/authorize?provider=${provider}&redirect_to=${redirectTo}`;
|
||||||
|
|
||||||
|
window.open(url, '_current');
|
||||||
|
}
|
@ -0,0 +1,489 @@
|
|||||||
|
import { DatabaseId, RowId, ViewId, ViewLayout } from '@/application/collab.type';
|
||||||
|
import { GlobalComment, Reaction } from '@/application/comment.type';
|
||||||
|
import { initGrantService, refreshToken } from '@/application/services/js-services/http/gotrue';
|
||||||
|
import { blobToBytes } from '@/application/services/js-services/http/utils';
|
||||||
|
import { AFCloudConfig } from '@/application/services/services.type';
|
||||||
|
import { getTokenParsed, invalidToken } from '@/application/session/token';
|
||||||
|
import { FolderView, User, Workspace } from '@/application/types';
|
||||||
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
export * from './gotrue';
|
||||||
|
|
||||||
|
let axiosInstance: AxiosInstance | null = null;
|
||||||
|
|
||||||
|
export function initAPIService(config: AFCloudConfig) {
|
||||||
|
if (axiosInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
axiosInstance = axios.create({
|
||||||
|
baseURL: config.baseURL,
|
||||||
|
});
|
||||||
|
|
||||||
|
initGrantService(config.gotrueURL);
|
||||||
|
|
||||||
|
axiosInstance.interceptors.request.use(
|
||||||
|
async (config) => {
|
||||||
|
const token = getTokenParsed();
|
||||||
|
|
||||||
|
Object.assign(config.headers, {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isExpired = dayjs().isAfter(dayjs.unix(token.expires_at));
|
||||||
|
|
||||||
|
let access_token = token.access_token;
|
||||||
|
const refresh_token = token.refresh_token;
|
||||||
|
|
||||||
|
if (isExpired) {
|
||||||
|
const newToken = await refreshToken(refresh_token);
|
||||||
|
|
||||||
|
access_token = newToken?.access_token || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (access_token) {
|
||||||
|
Object.assign(config.headers, {
|
||||||
|
Authorization: `Bearer ${access_token}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
axiosInstance.interceptors.response.use(async (response) => {
|
||||||
|
const status = response.status;
|
||||||
|
|
||||||
|
if (status === 401) {
|
||||||
|
const token = getTokenParsed();
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
invalidToken();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const refresh_token = token.refresh_token;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await refreshToken(refresh_token);
|
||||||
|
} catch (e) {
|
||||||
|
invalidToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signInWithUrl(url: string) {
|
||||||
|
const hash = new URL(url).hash;
|
||||||
|
|
||||||
|
if (!hash) {
|
||||||
|
return Promise.reject('No hash found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams(hash.slice(1));
|
||||||
|
const refresh_token = params.get('refresh_token');
|
||||||
|
|
||||||
|
if (!refresh_token) {
|
||||||
|
return Promise.reject('No access_token found');
|
||||||
|
}
|
||||||
|
|
||||||
|
await refreshToken(refresh_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifyToken(accessToken: string) {
|
||||||
|
const url = `/api/user/verify/${accessToken}`;
|
||||||
|
const response = await axiosInstance?.get<{
|
||||||
|
code: number;
|
||||||
|
data?: {
|
||||||
|
is_new: boolean;
|
||||||
|
};
|
||||||
|
message: string;
|
||||||
|
}>(url);
|
||||||
|
|
||||||
|
const data = response?.data;
|
||||||
|
|
||||||
|
if (data?.code === 0 && data.data) {
|
||||||
|
return data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCurrentUser(): Promise<User> {
|
||||||
|
const url = '/api/user/profile';
|
||||||
|
const response = await axiosInstance?.get<{
|
||||||
|
code: number;
|
||||||
|
data?: {
|
||||||
|
uid: number;
|
||||||
|
uuid: string;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
metadata: {
|
||||||
|
icon_url: string;
|
||||||
|
};
|
||||||
|
encryption_sign: null;
|
||||||
|
latest_workspace_id: string;
|
||||||
|
updated_at: number;
|
||||||
|
};
|
||||||
|
message: string;
|
||||||
|
}>(url);
|
||||||
|
|
||||||
|
const data = response?.data;
|
||||||
|
|
||||||
|
if (data?.code === 0 && data.data) {
|
||||||
|
const { uid, uuid, email, name, metadata } = data.data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
uid: String(uid),
|
||||||
|
uuid,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
avatar: metadata.icon_url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPublishViewMeta(namespace: string, publishName: string) {
|
||||||
|
const url = `/api/workspace/published/${namespace}/${publishName}`;
|
||||||
|
const response = await axiosInstance?.get(url);
|
||||||
|
|
||||||
|
return response?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPublishViewBlob(namespace: string, publishName: string) {
|
||||||
|
const url = `/api/workspace/published/${namespace}/${publishName}/blob`;
|
||||||
|
const response = await axiosInstance?.get(url, {
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
|
||||||
|
return blobToBytes(response?.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPublishView(publishNamespace: string, publishName: string) {
|
||||||
|
const meta = await getPublishViewMeta(publishNamespace, publishName);
|
||||||
|
const blob = await getPublishViewBlob(publishNamespace, publishName);
|
||||||
|
|
||||||
|
if (meta.view.layout === ViewLayout.Document) {
|
||||||
|
return {
|
||||||
|
data: blob,
|
||||||
|
meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoder = new TextDecoder('utf-8');
|
||||||
|
|
||||||
|
const jsonStr = decoder.decode(blob);
|
||||||
|
|
||||||
|
const res = JSON.parse(jsonStr) as {
|
||||||
|
database_collab: Uint8Array;
|
||||||
|
database_row_collabs: Record<RowId, number[]>;
|
||||||
|
database_row_document_collabs: Record<string, number[]>;
|
||||||
|
visible_database_view_ids: ViewId[];
|
||||||
|
database_relations: Record<DatabaseId, ViewId>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: new Uint8Array(res.database_collab),
|
||||||
|
rows: res.database_row_collabs,
|
||||||
|
visibleViewIds: res.visible_database_view_ids,
|
||||||
|
relations: res.database_relations,
|
||||||
|
meta,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPublishInfoWithViewId(viewId: string) {
|
||||||
|
const url = `/api/workspace/published-info/${viewId}`;
|
||||||
|
const response = await axiosInstance?.get<{
|
||||||
|
code: number;
|
||||||
|
data?: {
|
||||||
|
namespace: string;
|
||||||
|
publish_name: string;
|
||||||
|
};
|
||||||
|
message: string;
|
||||||
|
}>(url);
|
||||||
|
|
||||||
|
const data = response?.data;
|
||||||
|
|
||||||
|
if (data?.code === 0 && data.data) {
|
||||||
|
return data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPublishViewComments(viewId: string): Promise<GlobalComment[]> {
|
||||||
|
const url = `/api/workspace/published-info/${viewId}/comment`;
|
||||||
|
const response = await axiosInstance?.get<{
|
||||||
|
code: number;
|
||||||
|
data?: {
|
||||||
|
comments: {
|
||||||
|
comment_id: string;
|
||||||
|
user: {
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
avatar_url: string | null;
|
||||||
|
};
|
||||||
|
content: string;
|
||||||
|
created_at: string;
|
||||||
|
last_updated_at: string;
|
||||||
|
reply_comment_id: string | null;
|
||||||
|
is_deleted: boolean;
|
||||||
|
can_be_deleted: boolean;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
message: string;
|
||||||
|
}>(url);
|
||||||
|
|
||||||
|
const data = response?.data;
|
||||||
|
|
||||||
|
if (data?.code === 0 && data.data) {
|
||||||
|
const { comments } = data.data;
|
||||||
|
|
||||||
|
return comments.map((comment) => {
|
||||||
|
return {
|
||||||
|
commentId: comment.comment_id,
|
||||||
|
user: {
|
||||||
|
uuid: comment.user?.uuid || '',
|
||||||
|
name: comment.user?.name || '',
|
||||||
|
avatarUrl: comment.user?.avatar_url || null,
|
||||||
|
},
|
||||||
|
content: comment.content,
|
||||||
|
createdAt: comment.created_at,
|
||||||
|
lastUpdatedAt: comment.last_updated_at,
|
||||||
|
replyCommentId: comment.reply_comment_id,
|
||||||
|
isDeleted: comment.is_deleted,
|
||||||
|
canDeleted: comment.can_be_deleted,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getReactions(viewId: string, commentId?: string): Promise<Record<string, Reaction[]>> {
|
||||||
|
let url = `/api/workspace/published-info/${viewId}/reaction`;
|
||||||
|
|
||||||
|
if (commentId) {
|
||||||
|
url += `?comment_id=${commentId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axiosInstance?.get<{
|
||||||
|
code: number;
|
||||||
|
data?: {
|
||||||
|
reactions: {
|
||||||
|
reaction_type: string;
|
||||||
|
react_users: {
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
avatar_url: string | null;
|
||||||
|
}[];
|
||||||
|
comment_id: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
message: string;
|
||||||
|
}>(url);
|
||||||
|
|
||||||
|
const data = response?.data;
|
||||||
|
|
||||||
|
if (data?.code === 0 && data.data) {
|
||||||
|
const { reactions } = data.data;
|
||||||
|
const reactionsMap: Record<string, Reaction[]> = {};
|
||||||
|
|
||||||
|
for (const reaction of reactions) {
|
||||||
|
if (!reactionsMap[reaction.comment_id]) {
|
||||||
|
reactionsMap[reaction.comment_id] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
reactionsMap[reaction.comment_id].push({
|
||||||
|
reactionType: reaction.reaction_type,
|
||||||
|
commentId: reaction.comment_id,
|
||||||
|
reactUsers: reaction.react_users.map((user) => ({
|
||||||
|
uuid: user.uuid,
|
||||||
|
name: user.name,
|
||||||
|
avatarUrl: user.avatar_url,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return reactionsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createGlobalCommentOnPublishView(viewId: string, content: string, replyCommentId?: string) {
|
||||||
|
const url = `/api/workspace/published-info/${viewId}/comment`;
|
||||||
|
const response = await axiosInstance?.post<{ code: number; message: string }>(url, {
|
||||||
|
content,
|
||||||
|
reply_comment_id: replyCommentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.data.code === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(response?.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteGlobalCommentOnPublishView(viewId: string, commentId: string) {
|
||||||
|
const url = `/api/workspace/published-info/${viewId}/comment`;
|
||||||
|
const response = await axiosInstance?.delete<{ code: number; message: string }>(url, {
|
||||||
|
data: {
|
||||||
|
comment_id: commentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.data.code === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(response?.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addReaction(viewId: string, commentId: string, reactionType: string) {
|
||||||
|
const url = `/api/workspace/published-info/${viewId}/reaction`;
|
||||||
|
const response = await axiosInstance?.post<{ code: number; message: string }>(url, {
|
||||||
|
comment_id: commentId,
|
||||||
|
reaction_type: reactionType,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.data.code === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(response?.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeReaction(viewId: string, commentId: string, reactionType: string) {
|
||||||
|
const url = `/api/workspace/published-info/${viewId}/reaction`;
|
||||||
|
const response = await axiosInstance?.delete<{ code: number; message: string }>(url, {
|
||||||
|
data: {
|
||||||
|
comment_id: commentId,
|
||||||
|
reaction_type: reactionType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.data.code === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(response?.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWorkspaces(): Promise<Workspace[]> {
|
||||||
|
const query = new URLSearchParams({
|
||||||
|
include_member_count: 'true',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = `/api/workspace?${query.toString()}`;
|
||||||
|
const response = await axiosInstance?.get<{
|
||||||
|
code: number;
|
||||||
|
data?: {
|
||||||
|
workspace_id: string;
|
||||||
|
workspace_name: string;
|
||||||
|
member_count: number;
|
||||||
|
icon: string;
|
||||||
|
}[];
|
||||||
|
message: string;
|
||||||
|
}>(url);
|
||||||
|
|
||||||
|
const data = response?.data;
|
||||||
|
|
||||||
|
if (data?.code === 0 && data.data) {
|
||||||
|
return data.data.map((workspace) => {
|
||||||
|
return {
|
||||||
|
id: workspace.workspace_id,
|
||||||
|
name: workspace.workspace_name,
|
||||||
|
memberCount: workspace.member_count,
|
||||||
|
icon: workspace.icon,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkspaceFolder {
|
||||||
|
view_id: string;
|
||||||
|
icon: string | null;
|
||||||
|
name: string;
|
||||||
|
is_space: boolean;
|
||||||
|
is_private: boolean;
|
||||||
|
extra: {
|
||||||
|
is_space: boolean;
|
||||||
|
space_created_at: number;
|
||||||
|
space_icon: string;
|
||||||
|
space_icon_color: string;
|
||||||
|
space_permission: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
children: WorkspaceFolder[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function iterateFolder(folder: WorkspaceFolder): FolderView {
|
||||||
|
return {
|
||||||
|
id: folder.view_id,
|
||||||
|
name: folder.name,
|
||||||
|
icon: folder.icon,
|
||||||
|
isSpace: folder.is_space,
|
||||||
|
extra: folder.extra ? JSON.stringify(folder.extra) : null,
|
||||||
|
isPrivate: folder.is_private,
|
||||||
|
children: folder.children.map((child: WorkspaceFolder) => {
|
||||||
|
return iterateFolder(child);
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWorkspaceFolder(workspaceId: string): Promise<FolderView> {
|
||||||
|
const url = `/api/workspace/${workspaceId}/folder`;
|
||||||
|
const response = await axiosInstance?.get<{
|
||||||
|
code: number;
|
||||||
|
data?: WorkspaceFolder;
|
||||||
|
message: string;
|
||||||
|
}>(url);
|
||||||
|
|
||||||
|
const data = response?.data;
|
||||||
|
|
||||||
|
if (data?.code === 0 && data.data) {
|
||||||
|
return iterateFolder(data.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DuplicatePublishViewPayload {
|
||||||
|
published_collab_type: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||||
|
published_view_id: string;
|
||||||
|
dest_view_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function duplicatePublishView(workspaceId: string, payload: DuplicatePublishViewPayload) {
|
||||||
|
const url = `/api/workspace/${workspaceId}/published-duplicate`;
|
||||||
|
|
||||||
|
const res = await axiosInstance?.post<{
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
}>(url, payload);
|
||||||
|
|
||||||
|
if (res?.data.code === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(res?.data.message);
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export * as APIService from './http_api';
|
@ -0,0 +1,17 @@
|
|||||||
|
export function blobToBytes(blob: Blob): Promise<Uint8Array> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onloadend = () => {
|
||||||
|
if (!(reader.result instanceof ArrayBuffer)) {
|
||||||
|
reject(new Error('Failed to convert blob to bytes'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(new Uint8Array(reader.result));
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsArrayBuffer(blob);
|
||||||
|
});
|
||||||
|
}
|
@ -8,24 +8,8 @@ import {
|
|||||||
} from '@/application/services/js-services/cache';
|
} from '@/application/services/js-services/cache';
|
||||||
import { StrategyType } from '@/application/services/js-services/cache/types';
|
import { StrategyType } from '@/application/services/js-services/cache/types';
|
||||||
import { fetchPublishView, fetchPublishViewMeta, fetchViewInfo } from '@/application/services/js-services/fetch';
|
import { fetchPublishView, fetchPublishViewMeta, fetchViewInfo } from '@/application/services/js-services/fetch';
|
||||||
import {
|
import { APIService } from '@/application/services/js-services/http';
|
||||||
initAPIService,
|
|
||||||
signInGoogle,
|
|
||||||
signInWithMagicLink,
|
|
||||||
signInGithub,
|
|
||||||
signInDiscord,
|
|
||||||
signInWithUrl,
|
|
||||||
createGlobalCommentOnPublishView,
|
|
||||||
deleteGlobalCommentOnPublishView,
|
|
||||||
getPublishViewComments,
|
|
||||||
getWorkspaces,
|
|
||||||
getWorkspaceFolder,
|
|
||||||
getCurrentUser,
|
|
||||||
duplicatePublishView,
|
|
||||||
getReactions,
|
|
||||||
addReaction,
|
|
||||||
removeReaction,
|
|
||||||
} from '@/application/services/js-services/wasm/client_api';
|
|
||||||
import { AFService, AFServiceConfig } from '@/application/services/services.type';
|
import { AFService, AFServiceConfig } from '@/application/services/services.type';
|
||||||
import { emit, EventType } from '@/application/session';
|
import { emit, EventType } from '@/application/session';
|
||||||
import { afterAuth, AUTH_CALLBACK_URL, withSignIn } from '@/application/session/sign_in';
|
import { afterAuth, AUTH_CALLBACK_URL, withSignIn } from '@/application/session/sign_in';
|
||||||
@ -53,11 +37,7 @@ export class AFClientService implements AFService {
|
|||||||
private cacheDatabaseRowFolder: Map<string, Y.Map<YDoc>> = new Map();
|
private cacheDatabaseRowFolder: Map<string, Y.Map<YDoc>> = new Map();
|
||||||
|
|
||||||
constructor(config: AFServiceConfig) {
|
constructor(config: AFServiceConfig) {
|
||||||
initAPIService({
|
APIService.initAPIService(config.cloudConfig);
|
||||||
...config.cloudConfig,
|
|
||||||
deviceId: this.deviceId,
|
|
||||||
clientId: this.clientId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getClientId() {
|
getClientId() {
|
||||||
@ -182,7 +162,7 @@ export class AFClientService implements AFService {
|
|||||||
async loginAuth(url: string) {
|
async loginAuth(url: string) {
|
||||||
try {
|
try {
|
||||||
console.log('loginAuth', url);
|
console.log('loginAuth', url);
|
||||||
await signInWithUrl(url);
|
await APIService.signInWithUrl(url);
|
||||||
emit(EventType.SESSION_VALID);
|
emit(EventType.SESSION_VALID);
|
||||||
afterAuth();
|
afterAuth();
|
||||||
return;
|
return;
|
||||||
@ -194,51 +174,45 @@ export class AFClientService implements AFService {
|
|||||||
|
|
||||||
@withSignIn()
|
@withSignIn()
|
||||||
async signInMagicLink({ email }: { email: string; redirectTo: string }) {
|
async signInMagicLink({ email }: { email: string; redirectTo: string }) {
|
||||||
return await signInWithMagicLink(email, AUTH_CALLBACK_URL);
|
return await APIService.signInWithMagicLink(email, AUTH_CALLBACK_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@withSignIn()
|
@withSignIn()
|
||||||
async signInGoogle(_: { redirectTo: string }) {
|
async signInGoogle(_: { redirectTo: string }) {
|
||||||
return await signInGoogle(AUTH_CALLBACK_URL);
|
return APIService.signInGoogle(AUTH_CALLBACK_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@withSignIn()
|
@withSignIn()
|
||||||
async signInGithub(_: { redirectTo: string }) {
|
async signInGithub(_: { redirectTo: string }) {
|
||||||
return await signInGithub(AUTH_CALLBACK_URL);
|
return APIService.signInGithub(AUTH_CALLBACK_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@withSignIn()
|
@withSignIn()
|
||||||
async signInDiscord(_: { redirectTo: string }) {
|
async signInDiscord(_: { redirectTo: string }) {
|
||||||
return await signInDiscord(AUTH_CALLBACK_URL);
|
return APIService.signInDiscord(AUTH_CALLBACK_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWorkspaces() {
|
async getWorkspaces() {
|
||||||
const data = getWorkspaces();
|
const data = APIService.getWorkspaces();
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWorkspaceFolder(workspaceId: string) {
|
async getWorkspaceFolder(workspaceId: string) {
|
||||||
const data = await getWorkspaceFolder(workspaceId);
|
const data = await APIService.getWorkspaceFolder(workspaceId);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCurrentUser() {
|
async getCurrentUser() {
|
||||||
const data = await getCurrentUser();
|
const data = await APIService.getCurrentUser();
|
||||||
|
|
||||||
return {
|
await APIService.getWorkspaces();
|
||||||
uid: data.uid,
|
return data;
|
||||||
email: data.email,
|
|
||||||
name: data.name,
|
|
||||||
avatar: data.icon_url,
|
|
||||||
uuid: data.uuid,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async duplicatePublishView(params: DuplicatePublishView) {
|
async duplicatePublishView(params: DuplicatePublishView) {
|
||||||
return duplicatePublishView({
|
return APIService.duplicatePublishView(params.workspaceId, {
|
||||||
workspace_id: params.workspaceId,
|
|
||||||
dest_view_id: params.spaceViewId,
|
dest_view_id: params.spaceViewId,
|
||||||
published_view_id: params.viewId,
|
published_view_id: params.viewId,
|
||||||
published_collab_type: params.collabType,
|
published_collab_type: params.collabType,
|
||||||
@ -246,26 +220,26 @@ export class AFClientService implements AFService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createCommentOnPublishView(viewId: string, content: string, replyCommentId: string | undefined): Promise<void> {
|
createCommentOnPublishView(viewId: string, content: string, replyCommentId: string | undefined): Promise<void> {
|
||||||
return createGlobalCommentOnPublishView(viewId, content, replyCommentId);
|
return APIService.createGlobalCommentOnPublishView(viewId, content, replyCommentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteCommentOnPublishView(viewId: string, commentId: string): Promise<void> {
|
deleteCommentOnPublishView(viewId: string, commentId: string): Promise<void> {
|
||||||
return deleteGlobalCommentOnPublishView(viewId, commentId);
|
return APIService.deleteGlobalCommentOnPublishView(viewId, commentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPublishViewGlobalComments(viewId: string): Promise<GlobalComment[]> {
|
getPublishViewGlobalComments(viewId: string): Promise<GlobalComment[]> {
|
||||||
return getPublishViewComments(viewId);
|
return APIService.getPublishViewComments(viewId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPublishViewReactions(viewId: string, commentId?: string): Promise<Record<string, Reaction[]>> {
|
getPublishViewReactions(viewId: string, commentId?: string): Promise<Record<string, Reaction[]>> {
|
||||||
return getReactions(viewId, commentId);
|
return APIService.getReactions(viewId, commentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
addPublishViewReaction(viewId: string, commentId: string, reactionType: string): Promise<void> {
|
addPublishViewReaction(viewId: string, commentId: string, reactionType: string): Promise<void> {
|
||||||
return addReaction(viewId, commentId, reactionType);
|
return APIService.addReaction(viewId, commentId, reactionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
removePublishViewReaction(viewId: string, commentId: string, reactionType: string): Promise<void> {
|
removePublishViewReaction(viewId: string, commentId: string, reactionType: string): Promise<void> {
|
||||||
return removeReaction(viewId, commentId, reactionType);
|
return APIService.removeReaction(viewId, commentId, reactionType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,236 +0,0 @@
|
|||||||
import { getToken, invalidToken, isTokenValid, refreshToken } from '@/application/session/token';
|
|
||||||
import { ClientAPI, WorkspaceFolder, DuplicatePublishViewPayload } from '@appflowyinc/client-api-wasm';
|
|
||||||
import { AFCloudConfig } from '@/application/services/services.type';
|
|
||||||
import { DatabaseId, PublishViewMetaData, RowId, ViewId, ViewLayout } from '@/application/collab.type';
|
|
||||||
import { FolderView } from '@/application/types';
|
|
||||||
import { GlobalComment, Reaction } from '@/application/comment.type';
|
|
||||||
|
|
||||||
let client: ClientAPI;
|
|
||||||
|
|
||||||
export function initAPIService(
|
|
||||||
config: AFCloudConfig & {
|
|
||||||
deviceId: string;
|
|
||||||
clientId: string;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
if (client) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.refresh_token = refreshToken;
|
|
||||||
|
|
||||||
window.invalid_token = invalidToken;
|
|
||||||
|
|
||||||
client = ClientAPI.new({
|
|
||||||
base_url: config.baseURL,
|
|
||||||
ws_addr: config.wsURL,
|
|
||||||
gotrue_url: config.gotrueURL,
|
|
||||||
device_id: config.deviceId,
|
|
||||||
client_id: config.clientId,
|
|
||||||
configuration: {
|
|
||||||
compression_quality: 8,
|
|
||||||
compression_buffer_size: 10240,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isTokenValid()) {
|
|
||||||
client.restore_token(getToken() || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
client.subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPublishView(publishNamespace: string, publishName: string) {
|
|
||||||
const data = await client.get_publish_view(publishNamespace, publishName);
|
|
||||||
|
|
||||||
const meta = JSON.parse(data.meta.data) as PublishViewMetaData;
|
|
||||||
|
|
||||||
if (meta.view.layout === ViewLayout.Document) {
|
|
||||||
return {
|
|
||||||
data: data.data,
|
|
||||||
meta,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const decoder = new TextDecoder('utf-8');
|
|
||||||
|
|
||||||
const jsonStr = decoder.decode(new Uint8Array(data.data));
|
|
||||||
|
|
||||||
const res = JSON.parse(jsonStr) as {
|
|
||||||
database_collab: number[];
|
|
||||||
database_row_collabs: Record<RowId, number[]>;
|
|
||||||
database_row_document_collabs: Record<string, number[]>;
|
|
||||||
visible_database_view_ids: ViewId[];
|
|
||||||
database_relations: Record<DatabaseId, ViewId>;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: res.database_collab,
|
|
||||||
rows: res.database_row_collabs,
|
|
||||||
visibleViewIds: res.visible_database_view_ids,
|
|
||||||
relations: res.database_relations,
|
|
||||||
meta,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPublishInfoWithViewId(viewId: string) {
|
|
||||||
return client.get_publish_info(viewId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPublishViewMeta(publishNamespace: string, publishName: string) {
|
|
||||||
const data = await client.get_publish_view_meta(publishNamespace, publishName);
|
|
||||||
const metadata = JSON.parse(data.data) as PublishViewMetaData;
|
|
||||||
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signInWithUrl(url: string) {
|
|
||||||
return client.sign_in_with_url(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signInWithMagicLink(email: string, redirectTo: string) {
|
|
||||||
return client.sign_in_with_magic_link(email, redirectTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signInGoogle(redirectTo: string) {
|
|
||||||
return signInProvider('google', redirectTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signInProvider(provider: string, redirectTo: string) {
|
|
||||||
try {
|
|
||||||
const { url } = await client.generate_oauth_url_with_provider(provider, redirectTo);
|
|
||||||
|
|
||||||
window.open(url, '_current');
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signInGithub(redirectTo: string) {
|
|
||||||
return signInProvider('github', redirectTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signInDiscord(redirectTo: string) {
|
|
||||||
return signInProvider('discord', redirectTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getWorkspaces() {
|
|
||||||
try {
|
|
||||||
const { data } = await client.get_workspaces();
|
|
||||||
|
|
||||||
return data.map((workspace) => ({
|
|
||||||
id: workspace.workspace_id,
|
|
||||||
name: workspace.workspace_name,
|
|
||||||
icon: workspace.icon,
|
|
||||||
memberCount: workspace.member_count || 0,
|
|
||||||
}));
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getWorkspaceFolder(workspaceId: string): Promise<FolderView> {
|
|
||||||
try {
|
|
||||||
const data = await client.get_folder(workspaceId);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-inner-declarations
|
|
||||||
function iterateFolder(folder: WorkspaceFolder): FolderView {
|
|
||||||
return {
|
|
||||||
id: folder.view_id,
|
|
||||||
name: folder.name,
|
|
||||||
icon: folder.icon,
|
|
||||||
isSpace: folder.is_space,
|
|
||||||
extra: folder.extra,
|
|
||||||
isPrivate: folder.is_private,
|
|
||||||
children: folder.children.map((child: WorkspaceFolder) => {
|
|
||||||
return iterateFolder(child);
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return iterateFolder(data);
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentUser() {
|
|
||||||
return client.get_user();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function duplicatePublishView(payload: DuplicatePublishViewPayload) {
|
|
||||||
return client.duplicate_publish_view(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPublishViewComments(viewId: string): Promise<GlobalComment[]> {
|
|
||||||
try {
|
|
||||||
const { comments } = await client.get_publish_view_comments(viewId);
|
|
||||||
|
|
||||||
return comments.map((comment) => {
|
|
||||||
return {
|
|
||||||
commentId: comment.comment_id,
|
|
||||||
user: {
|
|
||||||
uuid: comment.user?.uuid || '',
|
|
||||||
name: comment.user?.name || '',
|
|
||||||
avatarUrl: comment.user?.avatar_url || null,
|
|
||||||
},
|
|
||||||
content: comment.content,
|
|
||||||
createdAt: comment.created_at,
|
|
||||||
lastUpdatedAt: comment.last_updated_at,
|
|
||||||
replyCommentId: comment.reply_comment_id,
|
|
||||||
isDeleted: comment.is_deleted,
|
|
||||||
canDeleted: comment.can_be_deleted,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createGlobalCommentOnPublishView(viewId: string, content: string, replyCommentId?: string) {
|
|
||||||
return client.create_comment_on_publish_view(viewId, content, replyCommentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteGlobalCommentOnPublishView(viewId: string, commentId: string) {
|
|
||||||
return client.delete_comment_on_publish_view(viewId, commentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getReactions(viewId: string, commentId?: string): Promise<Record<string, Reaction[]>> {
|
|
||||||
try {
|
|
||||||
const { reactions } = await client.get_reactions(viewId, commentId);
|
|
||||||
|
|
||||||
const reactionsMap: Record<string, Reaction[]> = {};
|
|
||||||
|
|
||||||
for (const reaction of reactions) {
|
|
||||||
if (!reactionsMap[reaction.comment_id]) {
|
|
||||||
reactionsMap[reaction.comment_id] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
reactionsMap[reaction.comment_id].push({
|
|
||||||
reactionType: reaction.reaction_type,
|
|
||||||
commentId: reaction.comment_id,
|
|
||||||
reactUsers: reaction.react_users.map((user) => ({
|
|
||||||
uuid: user.uuid,
|
|
||||||
name: user.name,
|
|
||||||
avatarUrl: user.avatar_url,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return reactionsMap;
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function addReaction(viewId: string, commentId: string, reactionType: string) {
|
|
||||||
return client.create_reaction(viewId, commentId, reactionType);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function removeReaction(viewId: string, commentId: string, reactionType: string) {
|
|
||||||
return client.delete_reaction(viewId, commentId, reactionType);
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * as APIService from './client_api';
|
|
@ -18,3 +18,21 @@ export function isTokenValid() {
|
|||||||
export function getToken() {
|
export function getToken() {
|
||||||
return localStorage.getItem('token');
|
return localStorage.getItem('token');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getTokenParsed(): {
|
||||||
|
access_token: string;
|
||||||
|
expires_at: number;
|
||||||
|
refresh_token: string;
|
||||||
|
} | null {
|
||||||
|
const token = getToken();
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(token);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { MAX_FREQUENTLY_ROW_COUNT, PER_ROW_EMOJI_COUNT } from '@/components/_shared/emoji-picker/const';
|
|
||||||
import { loadEmojiData } from '@/utils/emoji';
|
import { loadEmojiData } from '@/utils/emoji';
|
||||||
import { EmojiMartData } from '@emoji-mart/data';
|
import { EmojiMartData } from '@emoji-mart/data';
|
||||||
import { PopoverProps } from '@mui/material/Popover';
|
import { PopoverProps } from '@mui/material/Popover';
|
||||||
import { PopoverOrigin } from '@mui/material/Popover/Popover';
|
import { PopoverOrigin } from '@mui/material/Popover/Popover';
|
||||||
import { FrequentlyUsed, getEmojiDataFromNative, init, Store } from 'emoji-mart';
|
|
||||||
import chunk from 'lodash-es/chunk';
|
import chunk from 'lodash-es/chunk';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
@ -22,12 +20,12 @@ export function useLoadEmojiData({ onEmojiSelect }: { onEmojiSelect: (emoji: str
|
|||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
const [emojiCategories, setEmojiCategories] = useState<EmojiCategory[]>([]);
|
const [emojiCategories, setEmojiCategories] = useState<EmojiCategory[]>([]);
|
||||||
const [skin, setSkin] = useState<number>(() => {
|
const [skin, setSkin] = useState<number>(() => {
|
||||||
return Number(Store.get('skin')) || 0;
|
return Number(localStorage.getItem('emoji-mart.skin')) || 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSkinChange = useCallback((val: number) => {
|
const onSkinChange = useCallback((val: number) => {
|
||||||
setSkin(val);
|
setSkin(val);
|
||||||
Store.set('skin', String(val));
|
localStorage.setItem('emoji-mart.skin', String(val));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const searchEmojiData = useCallback(
|
const searchEmojiData = useCallback(
|
||||||
@ -70,10 +68,6 @@ export function useLoadEmojiData({ onEmojiSelect }: { onEmojiSelect: (emoji: str
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
await init({
|
|
||||||
maxFrequentRows: MAX_FREQUENTLY_ROW_COUNT,
|
|
||||||
perLine: PER_ROW_EMOJI_COUNT,
|
|
||||||
});
|
|
||||||
await searchEmojiData();
|
await searchEmojiData();
|
||||||
})();
|
})();
|
||||||
}, [searchEmojiData]);
|
}, [searchEmojiData]);
|
||||||
@ -85,17 +79,6 @@ export function useLoadEmojiData({ onEmojiSelect }: { onEmojiSelect: (emoji: str
|
|||||||
const onSelect = useCallback(
|
const onSelect = useCallback(
|
||||||
async (native: string) => {
|
async (native: string) => {
|
||||||
onEmojiSelect(native);
|
onEmojiSelect(native);
|
||||||
if (!native) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await getEmojiDataFromNative(native);
|
|
||||||
|
|
||||||
FrequentlyUsed.add(data);
|
|
||||||
} catch (e) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[onEmojiSelect]
|
[onEmojiSelect]
|
||||||
);
|
);
|
||||||
@ -156,6 +139,7 @@ export function getRowsWithCategories(emojiCategories: EmojiCategory[], rowSize:
|
|||||||
id: string;
|
id: string;
|
||||||
type: 'category' | 'emojis';
|
type: 'category' | 'emojis';
|
||||||
emojis?: Emoji[];
|
emojis?: Emoji[];
|
||||||
|
category?: string;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
emojiCategories.forEach((category) => {
|
emojiCategories.forEach((category) => {
|
||||||
@ -165,6 +149,7 @@ export function getRowsWithCategories(emojiCategories: EmojiCategory[], rowSize:
|
|||||||
});
|
});
|
||||||
chunk(category.emojis, rowSize).forEach((chunk, index) => {
|
chunk(category.emojis, rowSize).forEach((chunk, index) => {
|
||||||
rows.push({
|
rows.push({
|
||||||
|
category: category.id,
|
||||||
type: 'emojis',
|
type: 'emojis',
|
||||||
emojis: chunk,
|
emojis: chunk,
|
||||||
id: `${category.id}-${index}`,
|
id: `${category.id}-${index}`,
|
||||||
|
@ -67,17 +67,36 @@ function EmojiPickerCategories({
|
|||||||
const renderRow = useCallback(
|
const renderRow = useCallback(
|
||||||
({ index, style }: { index: number; style: React.CSSProperties }) => {
|
({ index, style }: { index: number; style: React.CSSProperties }) => {
|
||||||
const item = rows[index];
|
const item = rows[index];
|
||||||
|
const tagName = getCategoryName(item.id);
|
||||||
|
const isFlags = item.category === 'flags';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style} data-index={index}>
|
<div style={style} data-index={index}>
|
||||||
{item.type === 'category' ? (
|
{item.type === 'category' ? (
|
||||||
<div className={'pt-2 text-base font-medium text-text-caption'}>{getCategoryName(item.id)}</div>
|
<div className={'pt-2 text-base font-medium text-text-caption'}>{tagName}</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className={'flex'}>
|
<div className={'flex'}>
|
||||||
{item.emojis?.map((emoji, columnIndex) => {
|
{item.emojis?.map((emoji, columnIndex) => {
|
||||||
const isSelected = selectCell.row === index && selectCell.column === columnIndex;
|
const isSelected = selectCell.row === index && selectCell.column === columnIndex;
|
||||||
|
|
||||||
const isDefaultEmoji = defaultEmoji === emoji.native;
|
const isDefaultEmoji = defaultEmoji === emoji.native;
|
||||||
|
const classList = [
|
||||||
|
'flex cursor-pointer items-center justify-center rounded text-[20px] hover:bg-fill-list-hover',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
classList.push('bg-fill-list-hover');
|
||||||
|
} else {
|
||||||
|
classList.push('hover:bg-transparent');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDefaultEmoji) {
|
||||||
|
classList.push('bg-fill-list-active');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFlags) {
|
||||||
|
classList.push('icon');
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip key={emoji.id} title={emoji.name} placement={'top'} enterDelay={500} disableInteractive={true}>
|
<Tooltip key={emoji.id} title={emoji.name} placement={'top'} enterDelay={500} disableInteractive={true}>
|
||||||
@ -105,9 +124,7 @@ function EmojiPickerCategories({
|
|||||||
mouseX.current = e.clientX;
|
mouseX.current = e.clientX;
|
||||||
mouseY.current = e.clientY;
|
mouseY.current = e.clientY;
|
||||||
}}
|
}}
|
||||||
className={`flex cursor-pointer items-center justify-center rounded text-[20px] hover:bg-fill-list-hover ${
|
className={classList.join(' ')}
|
||||||
isSelected ? 'bg-fill-list-hover' : 'hover:bg-transparent'
|
|
||||||
} ${isDefaultEmoji ? 'bg-fill-list-active' : ''}`}
|
|
||||||
>
|
>
|
||||||
{emoji.native}
|
{emoji.native}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
import { lazy } from 'react';
|
// import { lazy } from 'react';
|
||||||
|
//
|
||||||
|
// export const EmojiPicker = lazy(() => import('./EmojiPicker'));
|
||||||
|
|
||||||
export const EmojiPicker = lazy(() => import('./EmojiPicker'));
|
export * from './EmojiPicker';
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ViewLayout, ViewMetaIcon } from '@/application/collab.type';
|
import { ViewLayout, ViewMetaIcon } from '@/application/collab.type';
|
||||||
import { ViewIcon } from '@/components/_shared/view-icon';
|
import { ViewIcon } from '@/components/_shared/view-icon';
|
||||||
import React from 'react';
|
import { isFlagEmoji } from '@/utils/emoji';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
function DatabaseHeader({
|
function DatabaseHeader({
|
||||||
@ -14,6 +15,9 @@ function DatabaseHeader({
|
|||||||
layout?: ViewLayout;
|
layout?: ViewLayout;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const isFlag = useMemo(() => {
|
||||||
|
return icon ? isFlagEmoji(icon.value) : false;
|
||||||
|
}, [icon]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -23,7 +27,7 @@ function DatabaseHeader({
|
|||||||
>
|
>
|
||||||
<div className={'relative'}>
|
<div className={'relative'}>
|
||||||
{icon?.value ? (
|
{icon?.value ? (
|
||||||
<div className={'view-icon'}>{icon?.value}</div>
|
<div className={`view-icon ${isFlag ? 'icon' : ''}`}>{icon?.value}</div>
|
||||||
) : (
|
) : (
|
||||||
<ViewIcon layout={layout || ViewLayout.Grid} size={10} />
|
<ViewIcon layout={layout || ViewLayout.Grid} size={10} />
|
||||||
)}
|
)}
|
||||||
|
@ -2,6 +2,7 @@ import { ViewLayout } from '@/application/collab.type';
|
|||||||
import { ViewMeta } from '@/application/db/tables/view_metas';
|
import { ViewMeta } from '@/application/db/tables/view_metas';
|
||||||
import { ViewIcon } from '@/components/_shared/view-icon';
|
import { ViewIcon } from '@/components/_shared/view-icon';
|
||||||
import { useEditorContext } from '@/components/editor/EditorContext';
|
import { useEditorContext } from '@/components/editor/EditorContext';
|
||||||
|
import { isFlagEmoji } from '@/utils/emoji';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -32,6 +33,10 @@ function MentionPage({ pageId }: { pageId: string }) {
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const isFlag = useMemo(() => {
|
||||||
|
return icon ? isFlagEmoji(icon.value) : false;
|
||||||
|
}, [icon]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -44,7 +49,7 @@ function MentionPage({ pageId }: { pageId: string }) {
|
|||||||
<span className={'mention-unpublished cursor-text font-semibold text-text-caption'}>No Access</span>
|
<span className={'mention-unpublished cursor-text font-semibold text-text-caption'}>No Access</span>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span className={'mention-icon icon'}>
|
<span className={`mention-icon ${isFlag ? 'icon' : ''}`}>
|
||||||
{icon?.value || <ViewIcon layout={meta?.layout || ViewLayout.Document} size={'small'} />}
|
{icon?.value || <ViewIcon layout={meta?.layout || ViewLayout.Document} size={'small'} />}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ export function useCommentRender(comment: GlobalComment) {
|
|||||||
}, [comment]);
|
}, [comment]);
|
||||||
|
|
||||||
const timeFormat = useMemo(() => {
|
const timeFormat = useMemo(() => {
|
||||||
const time = dayjs.unix(Number(comment.lastUpdatedAt));
|
const time = dayjs(comment.lastUpdatedAt);
|
||||||
|
|
||||||
return time.format('YYYY-MM-DD HH:mm:ss');
|
return time.format('YYYY-MM-DD HH:mm:ss');
|
||||||
}, [comment.lastUpdatedAt]);
|
}, [comment.lastUpdatedAt]);
|
||||||
@ -193,7 +193,7 @@ export function useCommentRender(comment: GlobalComment) {
|
|||||||
const time = useMemo(() => {
|
const time = useMemo(() => {
|
||||||
if (!comment.lastUpdatedAt) return '';
|
if (!comment.lastUpdatedAt) return '';
|
||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
const past = dayjs.unix(Number(comment.lastUpdatedAt));
|
const past = dayjs(comment.lastUpdatedAt);
|
||||||
const diffSec = now.diff(past, 'second');
|
const diffSec = now.diff(past, 'second');
|
||||||
const diffMin = now.diff(past, 'minute');
|
const diffMin = now.diff(past, 'minute');
|
||||||
const diffHour = now.diff(past, 'hour');
|
const diffHour = now.diff(past, 'hour');
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Reaction as ReactionType } from '@/application/comment.type';
|
import { Reaction as ReactionType } from '@/application/comment.type';
|
||||||
import { AFConfigContext } from '@/components/app/AppConfig';
|
import { AFConfigContext } from '@/components/app/AppConfig';
|
||||||
|
import { isFlagEmoji } from '@/utils/emoji';
|
||||||
import { getPlatform } from '@/utils/platform';
|
import { getPlatform } from '@/utils/platform';
|
||||||
import { Tooltip } from '@mui/material';
|
import { Tooltip } from '@mui/material';
|
||||||
import React, { memo, useContext, useMemo } from 'react';
|
import React, { memo, useContext, useMemo } from 'react';
|
||||||
@ -68,6 +69,10 @@ function Reaction({ reaction, onClick }: { reaction: ReactionType; onClick: (rea
|
|||||||
return getPlatform().isMobile;
|
return getPlatform().isMobile;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const isFlag = useMemo(() => {
|
||||||
|
return isFlagEmoji(reaction.reactionType);
|
||||||
|
}, [reaction.reactionType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
@ -91,7 +96,7 @@ function Reaction({ reaction, onClick }: { reaction: ReactionType; onClick: (rea
|
|||||||
'flex cursor-pointer items-center gap-1 rounded-full border border-transparent bg-fill-list-hover px-1 py-0.5 text-sm'
|
'flex cursor-pointer items-center gap-1 rounded-full border border-transparent bg-fill-list-hover px-1 py-0.5 text-sm'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className={''}>{reaction.reactionType}</span>
|
<span className={`${isFlag ? 'icon' : ''}`}>{reaction.reactionType}</span>
|
||||||
{<div className={'text-xs font-medium'}>{reactCount}</div>}
|
{<div className={'text-xs font-medium'}>{reactCount}</div>}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -4,6 +4,7 @@ import { notify } from '@/components/_shared/notify';
|
|||||||
import { ViewIcon } from '@/components/_shared/view-icon';
|
import { ViewIcon } from '@/components/_shared/view-icon';
|
||||||
import SpaceIcon from '@/components/publish/header/SpaceIcon';
|
import SpaceIcon from '@/components/publish/header/SpaceIcon';
|
||||||
import { renderColor } from '@/utils/color';
|
import { renderColor } from '@/utils/color';
|
||||||
|
import { isFlagEmoji } from '@/utils/emoji';
|
||||||
import { Tooltip } from '@mui/material';
|
import { Tooltip } from '@mui/material';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -34,6 +35,9 @@ function BreadcrumbItem({ crumb, disableClick = false }: { crumb: Crumb; disable
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const onNavigateToView = usePublishContext()?.toView;
|
const onNavigateToView = usePublishContext()?.toView;
|
||||||
|
const isFlag = useMemo(() => {
|
||||||
|
return icon ? isFlagEmoji(icon) : false;
|
||||||
|
}, [icon]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={name} placement={'bottom'} enterDelay={1000} enterNextDelay={1000}>
|
<Tooltip title={name} placement={'bottom'} enterDelay={1000} enterNextDelay={1000}>
|
||||||
@ -59,7 +63,7 @@ function BreadcrumbItem({ crumb, disableClick = false }: { crumb: Crumb; disable
|
|||||||
<SpaceIcon value={extraObj.space_icon || ''} />
|
<SpaceIcon value={extraObj.space_icon || ''} />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className={'icon flex h-5 w-5 items-center justify-center'}>
|
<span className={`${isFlag ? 'icon' : ''} flex h-5 w-5 items-center justify-center`}>
|
||||||
{icon || <ViewIcon layout={layout} size={'small'} />}
|
{icon || <ViewIcon layout={layout} size={'small'} />}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
@ -93,9 +93,7 @@ export function PublishViewHeader({ onOpenDrawer, openDrawer }: { onOpenDrawer:
|
|||||||
|
|
||||||
<div className={'flex items-center gap-2'}>
|
<div className={'flex items-center gap-2'}>
|
||||||
<MoreActions />
|
<MoreActions />
|
||||||
{/*<Suspense fallback={null}>*/}
|
{/*<Duplicate />*/}
|
||||||
{/* <Duplicate />*/}
|
|
||||||
{/*</Suspense>*/}
|
|
||||||
<Divider orientation={'vertical'} className={'mx-2'} flexItem />
|
<Divider orientation={'vertical'} className={'mx-2'} flexItem />
|
||||||
<Tooltip title={t('publish.downloadApp')}>
|
<Tooltip title={t('publish.downloadApp')}>
|
||||||
<button onClick={openOrDownload}>
|
<button onClick={openOrDownload}>
|
||||||
|
@ -5,6 +5,7 @@ import BuiltInImage4 from '@/assets/cover/m_cover_image_4.png';
|
|||||||
import BuiltInImage5 from '@/assets/cover/m_cover_image_5.png';
|
import BuiltInImage5 from '@/assets/cover/m_cover_image_5.png';
|
||||||
import BuiltInImage6 from '@/assets/cover/m_cover_image_6.png';
|
import BuiltInImage6 from '@/assets/cover/m_cover_image_6.png';
|
||||||
import ViewCover, { CoverType } from '@/components/view-meta/ViewCover';
|
import ViewCover, { CoverType } from '@/components/view-meta/ViewCover';
|
||||||
|
import { isFlagEmoji } from '@/utils/emoji';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ViewLayout, ViewMetaIcon } from '@/application/collab.type';
|
import { ViewLayout, ViewMetaIcon } from '@/application/collab.type';
|
||||||
@ -54,6 +55,10 @@ export function ViewMetaPreview({ icon, cover, name }: ViewMetaProps) {
|
|||||||
}, [coverType, cover?.value]);
|
}, [coverType, cover?.value]);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const isFlag = useMemo(() => {
|
||||||
|
return icon ? isFlagEmoji(icon.value) : false;
|
||||||
|
}, [icon]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex w-full flex-col items-center'}>
|
<div className={'flex w-full flex-col items-center'}>
|
||||||
{cover && <ViewCover coverType={coverType} coverValue={coverValue} />}
|
{cover && <ViewCover coverType={coverType} coverValue={coverValue} />}
|
||||||
@ -63,7 +68,7 @@ export function ViewMetaPreview({ icon, cover, name }: ViewMetaProps) {
|
|||||||
'flex gap-4 overflow-hidden whitespace-pre-wrap break-words break-all px-16 text-[2.25rem] font-bold leading-[1.5em] max-md:px-4 max-sm:text-[7vw]'
|
'flex gap-4 overflow-hidden whitespace-pre-wrap break-words break-all px-16 text-[2.25rem] font-bold leading-[1.5em] max-md:px-4 max-sm:text-[7vw]'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{icon?.value ? <div className={'view-icon'}>{icon?.value}</div> : null}
|
{icon?.value ? <div className={`view-icon ${isFlag ? 'icon' : ''}`}>{icon?.value}</div> : null}
|
||||||
|
|
||||||
<div className={'relative top-1.5'}>
|
<div className={'relative top-1.5'}>
|
||||||
{name || <span className={'text-text-placeholder'}>{t('menuAppHeader.defaultNewPageName')}</span>}
|
{name || <span className={'text-text-placeholder'}>{t('menuAppHeader.defaultNewPageName')}</span>}
|
||||||
|
@ -9,8 +9,8 @@ function LoginPage() {
|
|||||||
const isAuthenticated = useContext(AFConfigContext)?.isAuthenticated || false;
|
const isAuthenticated = useContext(AFConfigContext)?.isAuthenticated || false;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAuthenticated && redirectTo && encodeURIComponent(redirectTo) !== window.location.href) {
|
if (isAuthenticated && redirectTo && decodeURIComponent(redirectTo) !== window.location.href) {
|
||||||
window.location.href = redirectTo;
|
window.location.href = decodeURIComponent(redirectTo);
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, redirectTo]);
|
}, [isAuthenticated, redirectTo]);
|
||||||
return (
|
return (
|
||||||
|
@ -69,7 +69,6 @@ body {
|
|||||||
|
|
||||||
.view-icon {
|
.view-icon {
|
||||||
@apply flex w-fit leading-[1.5em] cursor-pointer rounded-lg py-2 text-[1.5em];
|
@apply flex w-fit leading-[1.5em] cursor-pointer rounded-lg py-2 text-[1.5em];
|
||||||
font-family: 'Apple Color Emoji', 'Noto Color Emoji', 'Segoe UI Emoji', 'Twemoji Mozilla', sans-serif;
|
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
@ -12,3 +12,7 @@ export async function randomEmoji(skin = 0) {
|
|||||||
export async function loadEmojiData() {
|
export async function loadEmojiData() {
|
||||||
return import('@emoji-mart/data/sets/15/native.json');
|
return import('@emoji-mart/data/sets/15/native.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isFlagEmoji(emoji: string) {
|
||||||
|
return /\uD83C[\uDDE6-\uDDFF]/.test(emoji);
|
||||||
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import svgr from 'vite-plugin-svgr';
|
import svgr from 'vite-plugin-svgr';
|
||||||
import wasm from 'vite-plugin-wasm';
|
|
||||||
import { visualizer } from 'rollup-plugin-visualizer';
|
import { visualizer } from 'rollup-plugin-visualizer';
|
||||||
import usePluginImport from 'vite-plugin-importer';
|
import usePluginImport from 'vite-plugin-importer';
|
||||||
import { totalBundleSize } from 'vite-plugin-total-bundle-size';
|
import { totalBundleSize } from 'vite-plugin-total-bundle-size';
|
||||||
@ -14,7 +13,6 @@ const isDev = process.env.NODE_ENV === 'development';
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
wasm(),
|
|
||||||
svgr({
|
svgr({
|
||||||
svgrOptions: {
|
svgrOptions: {
|
||||||
prettier: false,
|
prettier: false,
|
||||||
@ -117,7 +115,8 @@ export default defineConfig({
|
|||||||
id.includes('/react-custom-scrollbars') ||
|
id.includes('/react-custom-scrollbars') ||
|
||||||
id.includes('/dayjs') ||
|
id.includes('/dayjs') ||
|
||||||
id.includes('/smooth-scroll-into-view-if-needed') ||
|
id.includes('/smooth-scroll-into-view-if-needed') ||
|
||||||
id.includes('/react-virtualized-auto-sizer')
|
id.includes('/react-virtualized-auto-sizer') ||
|
||||||
|
id.includes('/react-window')
|
||||||
) {
|
) {
|
||||||
return 'common';
|
return 'common';
|
||||||
}
|
}
|
||||||
@ -141,13 +140,6 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: [
|
include: ['react', 'react-dom', 'react-katex'],
|
||||||
'react',
|
|
||||||
'react-dom',
|
|
||||||
'react-katex',
|
|
||||||
// 'react-custom-scrollbars-2',
|
|
||||||
// 'react-window',
|
|
||||||
// 'react-virtualized-auto-sizer',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user