mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2025-11-30 19:37:55 +08:00
chore: migrate appflowy.io to appflowy.com (#29)
This commit is contained in:
@@ -9,7 +9,7 @@ import { fetch } from 'bun';
|
|||||||
const distDir = path.join(__dirname, 'dist');
|
const distDir = path.join(__dirname, 'dist');
|
||||||
const indexPath = path.join(distDir, 'index.html');
|
const indexPath = path.join(distDir, 'index.html');
|
||||||
const baseURL = process.env.AF_BASE_URL as string;
|
const baseURL = process.env.AF_BASE_URL as string;
|
||||||
const defaultSite = 'https://appflowy.io';
|
const defaultSite = 'https://appflowy.com';
|
||||||
|
|
||||||
const setOrUpdateMetaTag = ($: CheerioAPI, selector: string, attribute: string, content: string) => {
|
const setOrUpdateMetaTag = ($: CheerioAPI, selector: string, attribute: string, content: string) => {
|
||||||
if ($(selector).length === 0) {
|
if ($(selector).length === 0) {
|
||||||
|
|||||||
@@ -2991,7 +2991,7 @@
|
|||||||
"second": "Unlimited pages & blocks",
|
"second": "Unlimited pages & blocks",
|
||||||
"three": "5 GB storage",
|
"three": "5 GB storage",
|
||||||
"four": "Intelligent search",
|
"four": "Intelligent search",
|
||||||
"five": "20 AI responses",
|
"five": "10 AI responses",
|
||||||
"six": "Mobile app",
|
"six": "Mobile app",
|
||||||
"seven": "Real-time collaboration"
|
"seven": "Real-time collaboration"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const AUTH_CALLBACK_PATH = '/auth/callback';
|
|||||||
export const AUTH_CALLBACK_URL = `${window.location.origin}${AUTH_CALLBACK_PATH}`;
|
export const AUTH_CALLBACK_URL = `${window.location.origin}${AUTH_CALLBACK_PATH}`;
|
||||||
|
|
||||||
export function withSignIn() {
|
export function withSignIn() {
|
||||||
return function (
|
return function(
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
_target: any,
|
_target: any,
|
||||||
_propertyKey: string,
|
_propertyKey: string,
|
||||||
@@ -23,14 +23,14 @@ export function withSignIn() {
|
|||||||
const originalMethod = descriptor.value;
|
const originalMethod = descriptor.value;
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
descriptor.value = async function (args: { redirectTo: string }) {
|
descriptor.value = async function(args: { redirectTo: string }) {
|
||||||
const redirectTo = args.redirectTo;
|
const redirectTo = args.redirectTo;
|
||||||
|
|
||||||
saveRedirectTo(redirectTo);
|
saveRedirectTo(redirectTo);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await originalMethod.apply(this, [args]);
|
await originalMethod.apply(this, [args]);
|
||||||
} catch (e) {
|
} catch(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
return Promise.reject(e);
|
return Promise.reject(e);
|
||||||
}
|
}
|
||||||
@@ -43,8 +43,15 @@ export function withSignIn() {
|
|||||||
export function afterAuth() {
|
export function afterAuth() {
|
||||||
const redirectTo = getRedirectTo();
|
const redirectTo = getRedirectTo();
|
||||||
|
|
||||||
if (redirectTo) {
|
if(redirectTo) {
|
||||||
clearRedirectTo();
|
clearRedirectTo();
|
||||||
window.location.href = decodeURIComponent(redirectTo);
|
const url = new URL(decodeURIComponent(redirectTo));
|
||||||
|
const pathname = url.pathname;
|
||||||
|
|
||||||
|
if(pathname === '/' || !pathname) {
|
||||||
|
url.pathname = '/app';
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = url.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function AppFlowyPower ({
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open('https://appflowy.io', '_blank');
|
window.open('https://appflowy.com', '_blank');
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width,
|
width,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default function Help () {
|
|||||||
<Button
|
<Button
|
||||||
component={'a'}
|
component={'a'}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={'https://www.appflowy.io/what-is-new'}
|
href={'https://www.appflowy.com/what-is-new'}
|
||||||
className={'justify-start'}
|
className={'justify-start'}
|
||||||
color={'inherit'}
|
color={'inherit'}
|
||||||
startIcon={<WhatsNewIcon />}
|
startIcon={<WhatsNewIcon />}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function MobileMore ({
|
|||||||
label: t('template.label'),
|
label: t('template.label'),
|
||||||
icon: <TemplateIcon />,
|
icon: <TemplateIcon />,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.open('https://appflowy.io/templates', '_blank');
|
window.open('https://appflowy.com/templates', '_blank');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { ReactComponent as TrashIcon } from '@/assets/trash.svg';
|
import { ReactComponent as TrashIcon } from '@/assets/trash.svg';
|
||||||
import { QuickNote } from '@/components/quick-note';
|
import { QuickNote } from '@/components/quick-note';
|
||||||
|
|
||||||
function SideBarBottom () {
|
function SideBarBottom() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ function SideBarBottom () {
|
|||||||
<IconButton
|
<IconButton
|
||||||
size={'small'}
|
size={'small'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open('https://appflowy.io/templates', '_blank');
|
window.open(`${window.location.origin}/templates`, '_blank');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TemplateIcon />
|
<TemplateIcon />
|
||||||
|
|||||||
@@ -12,21 +12,21 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { ReactComponent as DeleteIcon } from '@/assets/trash.svg';
|
import { ReactComponent as DeleteIcon } from '@/assets/trash.svg';
|
||||||
import { ReactComponent as EditIcon } from '@/assets/edit.svg';
|
import { ReactComponent as EditIcon } from '@/assets/edit.svg';
|
||||||
|
|
||||||
function TemplatePanel ({ viewId }: { viewId: string }) {
|
function TemplatePanel({ viewId }: { viewId: string }) {
|
||||||
const view = useAppView(viewId);
|
const view = useAppView(viewId);
|
||||||
const service = useService();
|
const service = useService();
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
|
const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
|
||||||
const [template, setTemplateInfo] = React.useState<Template>();
|
const [template, setTemplateInfo] = React.useState<Template>();
|
||||||
const loadTemplateInfo = useCallback(async () => {
|
const loadTemplateInfo = useCallback(async() => {
|
||||||
if (!service || !view?.view_id) return;
|
if(!service || !view?.view_id) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await service.getTemplateById(view?.view_id);
|
const res = await service.getTemplateById(view?.view_id);
|
||||||
|
|
||||||
setTemplateInfo(res);
|
setTemplateInfo(res);
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
} catch (e: any) {
|
} catch(e: any) {
|
||||||
// do nothing
|
// do nothing
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -38,9 +38,9 @@ function TemplatePanel ({ viewId }: { viewId: string }) {
|
|||||||
} = useLoadPublishInfo(viewId);
|
} = useLoadPublishInfo(viewId);
|
||||||
|
|
||||||
const url = useMemo(() => {
|
const url = useMemo(() => {
|
||||||
const origin = import.meta.env.AF_BASE_URL?.includes('test') ? 'https://test.appflowy.io' : 'https://appflowy.io';
|
const templateUrl = `${window.location.origin}/templates`;
|
||||||
|
|
||||||
return template ? `${origin}/templates/${slugify(template.categories[0].name)}/${template.view_id}` : '';
|
return template ? `${templateUrl}/${slugify(template.categories[0].name)}/${template.view_id}` : '';
|
||||||
}, [template]);
|
}, [template]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import './template.scss';
|
|||||||
import { slugify } from '@/components/as-template/utils';
|
import { slugify } from '@/components/as-template/utils';
|
||||||
import { ReactComponent as WebsiteIcon } from '@/assets/website.svg';
|
import { ReactComponent as WebsiteIcon } from '@/assets/website.svg';
|
||||||
|
|
||||||
function AsTemplate ({
|
function AsTemplate({
|
||||||
viewName,
|
viewName,
|
||||||
viewUrl,
|
viewUrl,
|
||||||
viewId,
|
viewId,
|
||||||
@@ -37,8 +37,8 @@ function AsTemplate ({
|
|||||||
loading,
|
loading,
|
||||||
} = useLoadTemplate(viewId);
|
} = useLoadTemplate(viewId);
|
||||||
|
|
||||||
const handleSubmit = useCallback(async (data: AsTemplateFormValue) => {
|
const handleSubmit = useCallback(async(data: AsTemplateFormValue) => {
|
||||||
if (!service || !selectedCreatorId || selectedCategoryIds.length === 0) return;
|
if(!service || !selectedCreatorId || selectedCategoryIds.length === 0) return;
|
||||||
const formData: UploadTemplatePayload = {
|
const formData: UploadTemplatePayload = {
|
||||||
...data,
|
...data,
|
||||||
view_id: viewId,
|
view_id: viewId,
|
||||||
@@ -50,7 +50,7 @@ function AsTemplate ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (template) {
|
if(template) {
|
||||||
await service?.updateTemplate(template.view_id, formData);
|
await service?.updateTemplate(template.view_id, formData);
|
||||||
} else {
|
} else {
|
||||||
await service?.createTemplate(formData);
|
await service?.createTemplate(formData);
|
||||||
@@ -59,7 +59,7 @@ function AsTemplate ({
|
|||||||
await loadTemplate();
|
await loadTemplate();
|
||||||
|
|
||||||
notify.success('Template saved successfully');
|
notify.success('Template saved successfully');
|
||||||
} catch (error) {
|
} catch(error) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
notify.error(error.toString());
|
notify.error(error.toString());
|
||||||
@@ -73,7 +73,7 @@ function AsTemplate ({
|
|||||||
}, [loadTemplate]);
|
}, [loadTemplate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!template) return;
|
if(!template) return;
|
||||||
setSelectedCategoryIds(template.categories.map((category) => category.id));
|
setSelectedCategoryIds(template.categories.map((category) => category.id));
|
||||||
setSelectedCreatorId(template.creator.id);
|
setSelectedCreatorId(template.creator.id);
|
||||||
setIsNewTemplate(template.is_new_template);
|
setIsNewTemplate(template.is_new_template);
|
||||||
@@ -81,7 +81,7 @@ function AsTemplate ({
|
|||||||
}, [template]);
|
}, [template]);
|
||||||
|
|
||||||
const defaultValue = useMemo(() => {
|
const defaultValue = useMemo(() => {
|
||||||
if (!template) return {
|
if(!template) return {
|
||||||
name: viewName,
|
name: viewName,
|
||||||
description: '',
|
description: '',
|
||||||
about: '',
|
about: '',
|
||||||
@@ -104,10 +104,11 @@ function AsTemplate ({
|
|||||||
startIcon={<WebsiteIcon />}
|
startIcon={<WebsiteIcon />}
|
||||||
variant={'text'}
|
variant={'text'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const origin = import.meta.env.AF_BASE_URL?.includes('test') ? 'https://test.appflowy.io' : 'https://appflowy.io';
|
const templateUrl = `${window.location.origin}/templates`;
|
||||||
|
|
||||||
window.open(`${origin}/templates/${slugify(template.categories[0].name)}/${template.view_id}`);
|
window.open(`${templateUrl}/${slugify(template.categories[0].name)}/${template.view_id}`);
|
||||||
}} color={'primary'}
|
}}
|
||||||
|
color={'primary'}
|
||||||
>{t('template.viewTemplate')}</Button>}
|
>{t('template.viewTemplate')}</Button>}
|
||||||
<div className={'flex items-center gap-2'}>
|
<div className={'flex items-center gap-2'}>
|
||||||
{template && <Button
|
{template && <Button
|
||||||
@@ -124,7 +125,9 @@ function AsTemplate ({
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
submitRef.current?.click();
|
submitRef.current?.click();
|
||||||
}} variant={'contained'} color={'primary'}
|
}}
|
||||||
|
variant={'contained'}
|
||||||
|
color={'primary'}
|
||||||
>
|
>
|
||||||
{t('button.save')}
|
{t('button.save')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -133,11 +136,15 @@ function AsTemplate ({
|
|||||||
</div>
|
</div>
|
||||||
<div className={'flex-1 flex gap-20 overflow-hidden'}>
|
<div className={'flex-1 flex gap-20 overflow-hidden'}>
|
||||||
<Paper className={'w-full h-full flex-1 flex justify-center overflow-hidden'}>
|
<Paper className={'w-full h-full flex-1 flex justify-center overflow-hidden'}>
|
||||||
<AFScroller className={'w-full h-full flex justify-center'} overflowXHidden>
|
<AFScroller
|
||||||
|
className={'w-full h-full flex justify-center'}
|
||||||
|
overflowXHidden
|
||||||
|
>
|
||||||
{loading ?
|
{loading ?
|
||||||
<CircularProgress /> :
|
<CircularProgress /> :
|
||||||
<AsTemplateForm
|
<AsTemplateForm
|
||||||
defaultValues={defaultValue} viewUrl={viewUrl}
|
defaultValues={defaultValue}
|
||||||
|
viewUrl={viewUrl}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
ref={submitRef}
|
ref={submitRef}
|
||||||
defaultRelatedTemplates={template?.related_templates}
|
defaultRelatedTemplates={template?.related_templates}
|
||||||
@@ -146,8 +153,14 @@ function AsTemplate ({
|
|||||||
</AFScroller>
|
</AFScroller>
|
||||||
</Paper>
|
</Paper>
|
||||||
<div className={'w-[25%] flex flex-col gap-4'}>
|
<div className={'w-[25%] flex flex-col gap-4'}>
|
||||||
<Categories value={selectedCategoryIds} onChange={setSelectedCategoryIds} />
|
<Categories
|
||||||
<Creator value={selectedCreatorId} onChange={setSelectedCreatorId} />
|
value={selectedCategoryIds}
|
||||||
|
onChange={setSelectedCategoryIds}
|
||||||
|
/>
|
||||||
|
<Creator
|
||||||
|
value={selectedCreatorId}
|
||||||
|
onChange={setSelectedCreatorId}
|
||||||
|
/>
|
||||||
<div className={'flex gap-2 items-center'}>
|
<div className={'flex gap-2 items-center'}>
|
||||||
<InputLabel>{t('template.isNewTemplate')}</InputLabel>
|
<InputLabel>{t('template.isNewTemplate')}</InputLabel>
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -37,10 +37,9 @@ export const withPasted = (editor: ReactEditor) => {
|
|||||||
|
|
||||||
if (isUrl) {
|
if (isUrl) {
|
||||||
const isAppFlowyLinkUrl = isURL(text, {
|
const isAppFlowyLinkUrl = isURL(text, {
|
||||||
host_whitelist: ['localhost', 'appflowy.com', 'test.appflowy.com', 'beta.appflowy.com'],
|
host_whitelist: [window.location.hostname],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('isAppFlowyLinkUrl', isAppFlowyLinkUrl);
|
|
||||||
if (isAppFlowyLinkUrl) {
|
if (isAppFlowyLinkUrl) {
|
||||||
const url = new URL(text);
|
const url = new URL(text);
|
||||||
const blockId = url.searchParams.get('blockId');
|
const blockId = url.searchParams.get('blockId');
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const NotFound = () => {
|
|||||||
<div className={'flex items-center px-2 max-sm:flex-col mt-4 w-full gap-4 justify-between'}>
|
<div className={'flex items-center px-2 max-sm:flex-col mt-4 w-full gap-4 justify-between'}>
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to="https://appflowy.io/download"
|
to="https://appflowy.com/download"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
className={'flex-1 py-3 px-4 max-sm:w-full rounded-[8px] max-md:text-base text-[20px] font-medium'}
|
className={'flex-1 py-3 px-4 max-sm:w-full rounded-[8px] max-md:text-base text-[20px] font-medium'}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function Login ({ redirectTo }: { redirectTo: string }) {
|
|||||||
>
|
>
|
||||||
<span>{t('web.signInAgreement')} </span>
|
<span>{t('web.signInAgreement')} </span>
|
||||||
<a
|
<a
|
||||||
href={'https://appflowy.io/terms'}
|
href={'https://appflowy.com/terms'}
|
||||||
target={'_blank'}
|
target={'_blank'}
|
||||||
className={'text-fill-default underline'}
|
className={'text-fill-default underline'}
|
||||||
>
|
>
|
||||||
@@ -36,7 +36,7 @@ export function Login ({ redirectTo }: { redirectTo: string }) {
|
|||||||
</a>{' '}
|
</a>{' '}
|
||||||
{t('web.and')}{' '}
|
{t('web.and')}{' '}
|
||||||
<a
|
<a
|
||||||
href={'https://appflowy.io/privacy'}
|
href={'https://appflowy.com/privacy'}
|
||||||
target={'_blank'}
|
target={'_blank'}
|
||||||
className={'text-fill-default underline'}
|
className={'text-fill-default underline'}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { ReactComponent as ErrorIcon } from '@/assets/error.svg';
|
import { ReactComponent as ErrorIcon } from '@/assets/error.svg';
|
||||||
|
|
||||||
function LoginAuth () {
|
function LoginAuth() {
|
||||||
const service = useContext(AFConfigContext)?.service;
|
const service = useContext(AFConfigContext)?.service;
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [modalOpened, setModalOpened] = useState(false);
|
const [modalOpened, setModalOpened] = useState(false);
|
||||||
@@ -16,13 +16,13 @@ function LoginAuth () {
|
|||||||
const openLoginModal = useContext(AFConfigContext)?.openLoginModal;
|
const openLoginModal = useContext(AFConfigContext)?.openLoginModal;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void (async () => {
|
void (async() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
await service?.loginAuth(window.location.href);
|
await service?.loginAuth(window.location.href);
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
} catch (e: any) {
|
} catch(e: any) {
|
||||||
setError(e.message);
|
setError(e.message);
|
||||||
setModalOpened(true);
|
setModalOpened(true);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -51,7 +51,7 @@ function LoginAuth () {
|
|||||||
closable={false}
|
closable={false}
|
||||||
cancelText={t('button.backToHome')}
|
cancelText={t('button.backToHome')}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
openLoginModal?.(getRedirectTo() || window.location.origin);
|
openLoginModal?.(getRedirectTo() || `${window.location.origin}/app`);
|
||||||
}}
|
}}
|
||||||
okText={t('button.tryAgain')}
|
okText={t('button.tryAgain')}
|
||||||
title={<div className={'text-left font-bold flex gap-2 items-center'}>
|
title={<div className={'text-left font-bold flex gap-2 items-center'}>
|
||||||
|
|||||||
@@ -112,8 +112,8 @@ export function openOrDownload (schema?: string) {
|
|||||||
const os = getOS();
|
const os = getOS();
|
||||||
|
|
||||||
if (os === 'ios' || os === 'android') {
|
if (os === 'ios' || os === 'android') {
|
||||||
const universalLink = 'https://appflowy.io/download';
|
const universalLink = 'https://appflowy.com/download';
|
||||||
const intentUrl = `intent://appflowy.io/download#Intent;` +
|
const intentUrl = `intent://appflowy.com/download#Intent;` +
|
||||||
'scheme=https;' +
|
'scheme=https;' +
|
||||||
'package=io.appflowy.app;' +
|
'package=io.appflowy.app;' +
|
||||||
`S.browser_fallback_url=${encodeURIComponent(androidDownloadLink)};` +
|
`S.browser_fallback_url=${encodeURIComponent(androidDownloadLink)};` +
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import isURL from 'validator/lib/isURL';
|
|||||||
import isIP from 'validator/lib/isIP';
|
import isIP from 'validator/lib/isIP';
|
||||||
import isFQDN from 'validator/lib/isFQDN';
|
import isFQDN from 'validator/lib/isFQDN';
|
||||||
|
|
||||||
export const downloadPage = 'https://appflowy.io/download';
|
export const downloadPage = 'https://appflowy.com/download';
|
||||||
|
|
||||||
export const openAppFlowySchema = 'appflowy-flutter://';
|
export const openAppFlowySchema = 'appflowy-flutter://';
|
||||||
|
|
||||||
export const iosDownloadLink = 'https://apps.apple.com/app/appflowy/id6457261352';
|
export const iosDownloadLink = 'https://apps.apple.com/app/appflowy/id6457261352';
|
||||||
export const androidDownloadLink = 'https://play.google.com/store/apps/details?id=io.appflowy.appflowy';
|
export const androidDownloadLink = 'https://play.google.com/store/apps/details?id=io.appflowy.appflowy';
|
||||||
|
|
||||||
export const desktopDownloadLink = 'https://appflowy.io/download/#pop';
|
export const desktopDownloadLink = 'https://appflowy.com/download/#pop';
|
||||||
|
|
||||||
export function isValidUrl(input: string) {
|
export function isValidUrl(input: string) {
|
||||||
return isURL(input, { require_protocol: true, require_host: false });
|
return isURL(input, { require_protocol: true, require_host: false });
|
||||||
|
|||||||
Reference in New Issue
Block a user