url#resolve TypeScript Examples

The following examples show how to use url#resolve. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: rehype-tweet.ts    From react-static-tweets with MIT License 6 votes vote down vote up
function visitAnchor(node) {
  if (!node.properties) return

  const { href } = node.properties

  if (!href) return

  const isAbsoluteUrl = ABSOLUTE_URL.test(href)

  if (!isAbsoluteUrl) {
    node.properties.href = resolve(TWITTER_URL, href)
  }
}
Example #2
Source File: Base.tsx    From hypertext with GNU General Public License v3.0 6 votes vote down vote up
export default function Base(): JSX.Element | null {
  // during SSR/SSG, don't specify a base tag
  if (isServerSide) {
    return null
  }

  // on the client, specify a "default" base tag, e.g. https://hypertext.finance/
  let href: string = resolve(window.location.origin, '/')

  // on the client, if this was an IPFS build, and if it seems like we're being served from a gateway of the form
  // e.g. https://ipfs.io/ipns/hypertext.finance/, specify a base tag of the gateway root for this page
  if (isIPFS && ['ipfs', 'ipns'].some((identifier) => identifier === window.location.pathname.split('/')[1])) {
    href = resolve(
      window.location.origin, // https://ipfs.io"
      window.location.pathname // /ipns/hypertext.finance/
        .split('/')
        .slice(0, 3)
        .join('/') + '/'
    )
  }

  return (
    <Head>
      <base key="base" href={href} />
    </Head>
  )
}
Example #3
Source File: rss.ts    From next-cms-ghost with MIT License 6 votes vote down vote up
generateRSSFeed = ({ posts, settings }: FeedProps) => {
  const { siteUrl } = settings.processEnv
  const feedOptions = {
    title: siteTitleMeta,
    description: siteDescriptionMeta,
    generator: `Jamify Blog Starter 2.0`,
    feed_url: resolve(siteUrl, 'rss/'),
    site_url: resolve(siteUrl, ''),
    image_url: resolve(siteUrl, siteIcon),
    ttl: 60,
    custom_namespaces: {
      content: `http://purl.org/rss/1.0/modules/content/`,
      media: `http://search.yahoo.com/mrss/`,
    },
  }
  const feed = new RSS(feedOptions)

  const feedItems = posts.map(post => generateItem({ post, settings }))

  feedItems.forEach(item => feed.item(item))

  return feed.xml({ indent: false })
}
Example #4
Source File: service.ts    From nestjs-keycloak-admin with MIT License 6 votes vote down vote up
constructor(options: KeycloakModuleOptions) {
    if (!options.baseUrl.startsWith('http')) {
      throw new Error(`Invalid base url. It should start with either http or https.`)
    }
    this.options = options
    this.baseUrl = resolve(options.baseUrl, `/auth/realms/${options.realmName}`)

    const keycloak: any = new KeycloakConnect({}, {
      resource: this.options.clientId,
      realm: this.options.realmName,
      'auth-server-url': resolve(this.options.baseUrl, '/auth'),
      secret: this.options.clientSecret,
    } as any)

    keycloak.accessDenied = (req: any, _res: any, next: any) => {
      req.accessDenied = true
      next()
    }

    this.connect = keycloak as Keycloak
    this.client = new AdminClient({
      baseUrl: this.options.baseUrl,
      realmName: this.options.realmName,
    })

    this.requestManager = new RequestManager(this, this.baseUrl)
  }
Example #5
Source File: _document.tsx    From next-cms-ghost with MIT License 6 votes vote down vote up
render() {
    const { pageProps } = this.props.__NEXT_DATA__.props
    const { cmsData, settings }  = pageProps || { cmsData: null, settings: null }
    const { settings: cmsSettings , bodyClass } = cmsData || { settings: null, bodyClass: '' }
    const { lang } = settings || cmsSettings || { lang: 'en' }

    return (
      <Html {...{lang, className: 'casper' }}>
        <Head>
          <link
            rel="alternate"
            type="application/rss+xml"
            title="Jamify RSS Feed"
            href={`${resolve(processEnv.siteUrl, 'rss.xml')}`}
          />
        </Head>
        <body {...{className: bodyClass}}>
          <script
            dangerouslySetInnerHTML={{
              __html: `
            (function(){
                window.isDark = localStorage.getItem('dark');
                if ( window.isDark === 'dark' ) {
                  document.body.classList.add('dark')
                } else if( window.isDark === undefined && window.matchMedia('(prefers-color-scheme: dark)').matches === true ){
                  document.body.classList.add('dark')
                }
            })()
          `,
            }}
          />
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
Example #6
Source File: download.ts    From devoirs with MIT License 6 votes vote down vote up
download = (from: string, to: string) =>
  new Promise<void>((resolve) => {
    const stream = createWriteStream(to);

    get(from, (response) => {
      response.pipe(stream);
      response.on('end', () => {
        stream.end();
        resolve();
      });
    });
  })
Example #7
Source File: download.ts    From devoirs with MIT License 6 votes vote down vote up
unzip = (from: string, to: string) =>
  new Promise((resolve, reject) => {
    const stream = createReadStream(from);
    const extract = Extract({ path: to });

    extract.on('error', reject);
    extract.once('close', resolve);
    stream.pipe(extract);
  })
Example #8
Source File: seoImage.ts    From next-cms-ghost with MIT License 6 votes vote down vote up
seoImage = async (props: SeoImageProps): Promise<ISeoImage> => {
  const { siteUrl, imageUrl, imageName } = props
  const defaultDimensions = { width: 1200, height: 800 }

  if (imageUrl) {
    const url = imageUrl
    const dimensions = (await imageDimensions(url)) || defaultDimensions
    return { url, dimensions }
  }

  const publicRoot = path.join(process.cwd(), 'public')
  const file = path.join(publicRoot, imageName || siteImage)

  const url = resolve(siteUrl, imageName || siteImage)
  if (existsSync(file)) {
    const dimensions = (await imageDimensionsFromFile(file)) || defaultDimensions
    return { url, dimensions }
  }
  const dimensions = (await imageDimensions(url)) || defaultDimensions

  return { url, dimensions }
}
Example #9
Source File: login.guard.ts    From Uber-ServeMe-System with MIT License 5 votes vote down vote up
canActivate(): Promise<boolean> {
    return new Promise(resolve => {
      this.auth.auth.onAuthStateChanged(user => {
        if(user) this.router.navigate(['home/feed'])
        resolve(!user ? true : false)
      })
    })
  }
Example #10
Source File: SocialRss.tsx    From next-cms-ghost with MIT License 5 votes vote down vote up
SocialRss = ({ siteUrl }: SocialRssProps) => (
  <a className="rss-button" href={`https://feedly.com/i/subscription/feed/${resolve(siteUrl, 'rss.xml')}`} target="_blank" rel="noopener noreferrer" title="Rss">
    <RssIcon />
  </a>
)
Example #11
Source File: Layout.tsx    From next-cms-ghost with MIT License 5 votes vote down vote up
Layout = ({ settings, header, children, isHome, sticky, previewPosts, bodyClass, errorClass }: LayoutProps) => {
  const lang = settings.lang
  const text = get(getLang(lang))
  const site = settings
  const title = text(`SITE_TITLE`, site.title)
  const { siteUrl, memberSubscriptions } = settings.processEnv

  const twitterUrl = site.twitter && `https://twitter.com/${site.twitter.replace(/^@/, ``)}`
  const facebookUrl = site.facebook && `https://www.facebook.com/${site.facebook.replace(/^\//, ``)}`

  errorClass = errorClass || ``

  return (
    <>
      <DocumentHead className={bodyClass} />

      <div className="site-wrapper">
        {/* The main header section on top of the screen */}
        {header}
        {/* The main content area */}
        <main ref={(isHome && sticky && sticky.anchorRef) || null} id="site-main" className={`site-main outer ${errorClass}`}>
          {/* All the main content gets inserted here, index.js, post.js */}
          {children}
        </main>
        {/* For sticky nav bar */}
        {isHome && <StickyNav className={`site-nav ${sticky && sticky.state.currentClass}`} {...{ siteUrl, settings }} />}
        {/* Links to Previous/Next posts */}
        {previewPosts}

        {/* The footer at the very bottom of the screen */}
        <footer className="site-footer outer">
          <div className="site-footer-content inner">
            <section className="copyright">
              <a href={resolve(siteUrl, '')}>{title}</a> &copy; {new Date().getFullYear()}
            </section>

            <nav className="site-footer-nav">
              <Link href="/">
                <a>{text(`LATEST_POSTS`)}</a>
              </Link>
              {site.facebook && (
                <a href={facebookUrl} target="_blank" rel="noopener noreferrer">
                  Facebook
                </a>
              )}
              {site.twitter && (
                <a href={twitterUrl} target="_blank" rel="noopener noreferrer">
                  Twitter
                </a>
              )}
              <a href="https://www.jamify.org" target="_blank" rel="noopener noreferrer">
                Jamify
              </a>
            </nav>
          </div>
        </footer>
      </div>

      {memberSubscriptions && <SubscribeSuccess {...{ title, lang }} />}

      {/* The big email subscribe modal content */}
      {memberSubscriptions && <SubscribeOverlay {...{ settings }} />}
    </>
  )
}
Example #12
Source File: PostController.ts    From typescript-clean-architecture with MIT License 5 votes vote down vote up
private setFileStorageBasePath(posts: PostUseCaseDto[]): void {
    posts.forEach((post: PostUseCaseDto) => {
      if (post.image) {
        post.image.url = resolve(FileStorageConfig.BASE_PATH, post.image.url);
      }
    });
  }
Example #13
Source File: MediaController.ts    From typescript-clean-architecture with MIT License 5 votes vote down vote up
private setFileStorageBasePath(medias: MediaUseCaseDto[]): void {
    medias.forEach((media: MediaUseCaseDto) => media.url = resolve(FileStorageConfig.BASE_PATH, media.url));
  }
Example #14
Source File: download.ts    From devoirs with MIT License 5 votes vote down vote up
getUrl = (target: Target) =>
  resolve(
    baseUrl,
    `${target.prefix}%2F${revision}%2F${target.name}.zip?alt=media`
  )
Example #15
Source File: auth.guard.ts    From Uber-ServeMe-System with MIT License 5 votes vote down vote up
canActivate(): Promise<boolean> {
    return new Promise(resolve => {
      this.auth.auth.onAuthStateChanged(user => {
        if(!user) this.router.navigate(['login'])
        resolve(user ? true : false)
      })
    })
  }
Example #16
Source File: dlc-fetch.ts    From elemental4 with GNU General Public License v3.0 4 votes vote down vote up
async function addDLCByUrl2(url: string, intendedType: DLCType, isBuiltIn = false): Promise<ThemeEntry | object | null> {
  let json, cors
  try {
    json = JSON.parse(url);
    url = location.origin + '/'
    if (json.type === 'server') {
      throw new Error("Cannot use inline JSON for a Server");
    }
  } catch (error) {
    if (!url.endsWith('.json')) {
      url += (url.endsWith('/') ? '' : '/') + 'elemental.json';
    }
    if (!url.match(/^[a-zA-Z-]+:\/\//) && !isBuiltIn) {
      if (url.startsWith('localhost')) {
        url = 'http://' + url;
      } else {
        url = 'https://' + url;
      }
    }
    try {
      const x = await fetchCorsAnywhere(url).then(async(x) => ({ cors: x.cors, response: await x.response.json() }));
      json = x.response;
      cors = x.cors;
    } catch (error) {
      if (isBuiltIn) {
        return null;
      }
      await AlertDialog({ title: 'Error Adding DLC', text: 'Could not find an elemental.json file at this URL.' });
      return null;
    }
  }
  if(!(
    typeof json === 'object'
    && json !== null
    && typeof json.type === 'string'
  )) {
    if (isBuiltIn) {
      return null;
    }
    await AlertDialog({ title: 'Error Adding DLC', text: 'Found a malformed elemental.json file' });
    return null;
  }
  
  if ((await getSupportedServerTypes()).includes(json.type)) {
    if(!(
      'name' in json
    )) {
      if (isBuiltIn) {
        return null;
      }
      await AlertDialog({ title: 'Error Adding DLC', text: 'The specified server is missing metadata.' });
      return null;
    }

    if (intendedType !== 'server' && (isBuiltIn || !await ConfirmDialog({ title: 'Not a ' + capitalize(intendedType), text: 'The url you provided points to an Elemental 4 Server, would you still like to add it?', trueButton: 'Continue' }))) {
      return null;
    }
    if (!cors) {
      if(!isBuiltIn) await AlertDialog({ title: 'Error Adding Server', text: 'This server does not have CORS Headers. Elemental 4 is not allowed to communicate with it.' })
      return null;
    }
    
    if(json.icon) {
      json.icon = resolve(url, json.icon);
    }

    await installServer(url.replace(/elemental\.json$/, ''), json);
    if(!isBuiltIn) {
      await connectApi(url.replace(/elemental\.json$/, ''), json);
    }
  } else if (json.type === 'elemental4:theme') {
    if(!(
      'format_version' in json &&
      'id' in json &&
      'name' in json &&
      'description' in json
    )) {
      if (isBuiltIn) {
        return null;
      }
      await AlertDialog({ title: 'Error Adding DLC', text: 'The specified theme is missing metadata.' });
      return null;
    }
    
    if (intendedType !== 'theme' && !await ConfirmDialog({ title: 'Not a ' + capitalize(intendedType), text: 'The url you provided points to an Elemental 4 Theme, would you still like to add it?', trueButton: 'Continue'} )) {
      return null;
    }
    if (json.format_version < THEME_VERSION && !await ConfirmDialog({ title: 'Incorrect Theme Version', text: 'This theme was made for an older version of Elemental 4, would you still like to use it.', trueButton: 'Continue'} )) {
      return null;
    }
    if (json.format_version > THEME_VERSION && !await ConfirmDialog({ title: 'Incorrect Theme Version', text: 'This theme was made for an older version of Elemental 4, would you still like to use it.', trueButton: 'Continue'} )) {
      return null;
    }

    const themeCache = await caches.open('THEME.' + json.id);
    
    if(json.styles) {
      const styles = await fetchCorsAnywhere(resolve(url, json.styles)).then(x => x.response.text());
      const styleURL = `/cache_data/${json.id}/style`;
      await themeCache.put(
        styleURL,
        new Response(new Blob([styles], { type: 'text/css' }), { status: 200 })
      );
      json.styles = styleURL;
    }
    if(json.sketch) {
      const sketch = await fetchCorsAnywhere(resolve(url, json.sketch)).then(x => x.response.text());
      const sketchURL = `/cache_data/${json.id}/sketch`;
      await themeCache.put(
        sketchURL,
        new Response(new Blob([sketch], { type: 'text/javascript' }), { status: 200 })
      );
      json.sketch = sketchURL;
    }
    if(json.icon) {
      const icon = (await fetchCorsAnywhere(resolve(url, json.icon))).response;
      const iconURL = `/cache_data/${json.id}/icon`;
      await themeCache.put(iconURL, icon.clone());
      json.icon = iconURL;
    }
    if(json.sounds) {
      const cachedSoundURLs = [];
      json.sounds = Object.fromEntries(await Promise.all(Object.keys(json.sounds).map(async(key) => {
        return [key, await Promise.all(json.sounds[key].map(async(x) => {
          const soundURL = `/cache_data/${json.id}/audio/${encodeURIComponent(x.url)}`;
          if (!cachedSoundURLs.includes(x.url)){
            const sound = (await fetchCorsAnywhere(resolve(url, x.url))).response;
            await themeCache.put(soundURL, sound.clone());
            cachedSoundURLs.push(x.url);
          }
          return {
            ...x,
            url: soundURL
          };
        }))];
      })))
    }
    if(json.music) {
      const cachedSoundURLs = [];
      json.music = await Promise.all(json.music.map(async(track) => {
        const musicURL = `/cache_data/${json.id}/audio/${encodeURIComponent(track.url)}`;
        if (!cachedSoundURLs.includes(track.url)){
          const sound = (await fetchCorsAnywhere(resolve(url, track.url))).response;
          await themeCache.put(musicURL, sound.clone());
          cachedSoundURLs.push(track.url);
        }
        return {
          ...track,
          url: musicURL
        };
      }))
    }

    json.isBuiltIn = isBuiltIn;
    if(isBuiltIn) {
      json.version = version;
    }

    await installTheme(json, true);
  } else if (json.type === 'pack') {
    if (isBuiltIn) {
      return null;
    }
    await AlertDialog({ title: 'Error Adding DLC', text: 'Singleplayer mode has not been added yet.' });
    return null;
  } else {
    if (isBuiltIn) {
      return null;
    }
    await AlertDialog({ title: 'Error Adding DLC', text: 'Don\'t know how to add server type "' + json.type + '"' });
    return null;
  }
}
Example #17
Source File: api.ts    From elemental4 with GNU General Public License v3.0 4 votes vote down vote up
export async function connectApi(baseUrl: string, config: ElementalConfig, ui?: ElementalLoadingUi) {
  baseUrl = baseUrl.replace(/\/(elemental\.json)?$/, '');
  
  if (currentAPI) {
    try {
      currentSaveFileAPI.close();
      disposeServerConfigGui();
      currentAPI.close();
    } catch (error) {
      console.error('Could not close the current API. This will probably cause a memory leak.');
      console.error(error);
    }
    currentAPI = null;
    currentSaveFileAPI = null;
  }

  let selfMadeUi = false;
  if(!ui) {
    ui = createLoadingUi();
    selfMadeUi = true;
  }
  await endStatistics()
  try {
    const json = config
      || builtInApis[baseUrl]
      || (await fetch(baseUrl + '/elemental.json')
        .then(x => x.json())
        .catch(async() => {
          return (await getServer(baseUrl)).config;
        }));

    if (!json) {
      throw new Error('Could not find Server.');
    }
    installServer(baseUrl, json);

    const API = apiTypeMap[json.type];
    
    currentSaveFileAPI = await getAPISaveFile(baseUrl);

    const api = new API({
      baseUrl,
      config: json,
      saveFile: currentSaveFileAPI,
      ui: {
        alert: AlertDialog,
        confirm: ConfirmDialog,
        prompt: PromptDialog,
        dialog: CustomDialog,
        popup: (o) => Promise.resolve(null),
        loading: async(cb) => {
          const ui = createLoadingUi();
          const r = await cb(ui);
          ui.dispose();
          return r;
        },
        reloadSelf: async() => {
          await connectApi(baseUrl, config, null);
        }
      },
      store: json.type.startsWith('internal:')
        ? new ChunkedStore('data.' + json.type.slice(9))
        : new ChunkedStore(json.type + ':' + processBaseUrl(baseUrl))
    });
    let isOpen = await api.open(ui);
    if (!isOpen) {
      try {
        api.close();
      } catch (error) {
        //
      }
      throw new Error("Could not open API connection.");
    }

    currentAPI = api;
    ClearElementGameUi();

    (document.querySelector('#element-sidebar') as HTMLElement).style.display = getSubAPI(api, 'suggestion') ? 'block' : 'none';
    (document.querySelector('#null_server') as HTMLElement).style.display = api[IsNullAPI] ? 'flex' : 'none';
    document.querySelector('#server-name').innerHTML = api[IsNullAPI] ? '' : '<b>Server:</b> ' + escapeHTML(`${json.name || `Untitled Server (type=${json.type})`}${baseUrl.startsWith('internal:') ? '' : ` — ${baseUrl}`}`);
    document.querySelector('#server-title').innerHTML = escapeHTML(json.name || `Unnamed Server (type=${json.type})`);
    document.querySelector('#server-description').innerHTML = escapeHTML(json.description || `[No description provided]`);
    if (json.icon) {
      document.querySelector('#server-icon').setAttribute('style', `background-image:url(${resolve(baseUrl, json.icon)});background-size:cover;`)
    } else {
      document.querySelector('#server-icon').setAttribute('style', `background-color:#888;`)
    }

    if (allBuiltInServers.includes(baseUrl)) {
      document.querySelector('#server-remove').setAttribute('disabled', 'true');
    } else {
      document.querySelector('#server-remove').removeAttribute('disabled');
    }

    ui.status('Loading News');

    const optionsApi = getSubAPI(currentAPI, 'optionsMenu');
    if (optionsApi) {
      reRenderServerConfigGui(optionsApi);
    }

    ui.status('Loading News');
    await InitElementNews();

    await onSaveFileLoad(ui);

    ui.status('Starting Statistics');

    await startStatistics();

    if(selfMadeUi) {
      (ui as any).dispose();
    }

    setActiveServer(baseUrl);

    return true;
  } catch (error) {
    console.error(error);
    await AlertDialog({ title: 'Error Connecting', text: `Failed to connect to ${baseUrl}.` });
    await connectApi('internal:null', null, ui);
  }
}
Example #18
Source File: Settings.tsx    From hypertext with GNU General Public License v3.0 4 votes vote down vote up
export default function Settings({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }): JSX.Element {
  const { chainId } = useWeb3React()
  const { colorMode, toggleColorMode } = useColorMode()
  const { pathname } = useRouter()

  useBodyKeyDown('d', toggleColorMode)

  const [approveMax, toggleApproveMax] = useApproveMax()
  const [deadline, setDeadline] = useDeadline()
  const [slippage, setSlippage] = useSlippage()

  const [firstToken] = useFirstToken()
  const [secondToken] = useSecondToken()

  let permalink: string | null = null
  if (typeof chainId === 'number' && (firstToken || secondToken) && (pathname === '/buy' || pathname === '/sell')) {
    const permalinkParameters = {
      [QueryParameters.CHAIN]: chainId.toString(),
      ...(pathname === '/buy'
        ? {
            ...(firstToken ? { [QueryParameters.OUTPUT]: (firstToken as Token).address } : {}),
            ...(secondToken ? { [QueryParameters.INPUT]: (secondToken as Token).address } : {}),
          }
        : {
            ...(firstToken ? { [QueryParameters.INPUT]: (firstToken as Token).address } : {}),
            ...(secondToken ? { [QueryParameters.OUTPUT]: (secondToken as Token).address } : {}),
          }),
    }
    permalink = resolve('https://hypertext.finance', `${pathname}${formatQueryParams(permalinkParameters)}`)
  }

  const [copied, setCopied] = useState(false)
  useEffect(() => {
    if (copied) {
      const timeout = setTimeout(() => {
        setCopied(false)
      }, 750)
      return (): void => {
        clearTimeout(timeout)
      }
    }
  }, [copied])
  function copyWithFlag(content: string): void {
    copy(content)
    setCopied(true)
  }

  return (
    <Modal isOpen={isOpen} onClose={onClose} isCentered>
      <ModalOverlay />
      <ModalContent color={COLOR[colorMode]}>
        <ModalHeader>
          <Text>Settings</Text>
        </ModalHeader>
        <ModalCloseButton />
        <ModalBody>
          <Stack direction="column">
            <Stack direction="row" justify="space-between">
              <Text>Dark Mode</Text>
              <Switch isChecked={colorMode === 'dark'} onChange={toggleColorMode} />
            </Stack>
            <Stack direction="row" justify="space-between">
              <Text>Approve Max</Text>
              <Switch isChecked={approveMax} onChange={toggleApproveMax} />
            </Stack>
            <Stack direction="row" justify="space-between">
              <Text>Deadline</Text>
              <Stack direction="column" spacing={0} alignItems="flex-end" w="50%" flexShrink={0}>
                <Slider min={60} max={60 * 60} step={60} value={deadline} onChange={setDeadline}>
                  <SliderTrack />
                  <SliderFilledTrack />
                  <SliderThumb />
                </Slider>
                <Stack direction="row" minHeight="1.5rem">
                  {deadline !== DEFAULT_DEADLINE && (
                    <Button
                      size="xs"
                      onClick={(): void => {
                        setDeadline(DEFAULT_DEADLINE)
                      }}
                    >
                      Reset
                    </Button>
                  )}
                  <Text>
                    {deadline / 60} {deadline === 60 ? 'minute' : 'minutes'}
                  </Text>
                </Stack>
              </Stack>
            </Stack>
            <Stack direction="row" justify="space-between">
              <Text>Slippage Tolerance</Text>
              <Stack direction="column" spacing={0} alignItems="flex-end" w="50%" flexShrink={0}>
                <Slider min={0} max={100 * 4} step={10} value={slippage} onChange={setSlippage}>
                  <SliderTrack />
                  <SliderFilledTrack />
                  <SliderThumb />
                </Slider>
                <Stack direction="row" minHeight="1.5rem">
                  {slippage !== DEFAULT_SLIPPAGE && (
                    <Button
                      size="xs"
                      onClick={(): void => {
                        setSlippage(DEFAULT_SLIPPAGE)
                      }}
                    >
                      Reset
                    </Button>
                  )}
                  <Text>{(slippage / 100).toFixed(slippage === 0 ? 0 : 1)}%</Text>
                </Stack>
              </Stack>
            </Stack>
          </Stack>
        </ModalBody>
        <ModalFooter justifyContent={permalink === null ? 'flex-end' : 'space-between'}>
          {typeof permalink === 'string' && (
            <Button
              variant="link"
              isDisabled={copied}
              color="blue.500"
              width="min-content"
              onClick={(): void => {
                try {
                  // eslint-disable-next-line
                  ;(window.navigator as any).share({ title: 'Hypertext', url: permalink }).catch(() => {})
                } catch {
                  copyWithFlag(permalink as string)
                }
              }}
            >
              {copied ? 'Copied' : 'Share Permalink'}
            </Button>
          )}

          <Link
            href={`https://github.com/NoahZinsmeister/hypertext/tree/${process.env.COMMIT_SHA}`}
            target="_blank"
            rel="noopener noreferrer"
            color="blue.500"
          >
            {process.env.COMMIT_SHA?.slice(0, 7)}
          </Link>
        </ModalFooter>
      </ModalContent>
    </Modal>
  )
}