OurBigBook logoOurBigBook Docs OurBigBook logoOurBigBook.comSite Source code
web/front/routes.js
const { commentsHeaderId, commentIdPrefix, escapeUsername } = require('./config');

const { encodeGetParams } = require('ourbigbook/web_api');
const { DIR_PREFIX, encodeUrlPath } = require('ourbigbook');

function getPage(page) {
  return page === undefined || page === 1 ? '' : `/${page}`
}

function decodeUrlPath(path) {
  try {
    return decodeURIComponent(path)
  } catch (_) {
    return path
  }
}

// Fragments allow all path characters and literal question marks (RFC 3986).
function encodeUrlFragment(fragment) {
  return encodeUrlPath(fragment).replace(/%3F/g, '?')
}

const encodeGetParamsWithPage = (opts, opts2={}) => {
  opts = Object.assign({}, opts)
  if (opts.page === 1) {
    delete opts.page
  }
  const defaultSort = opts2.defaultSort || 'created'
  if (opts.sort === defaultSort) {
    delete opts.sort
  }
  return encodeGetParams(opts)
}

function articleScope(slug) {
  return `/${encodeUrlPath(slug)}/${escapeUsername}${slug.includes('/') ? '' : '/home'}`
}

function issue(slug, number) {
  return `/${encodeUrlPath(slug)}/${escapeUsername}/discussion/${number}`
}

// Router.pathname identifies the internal Next page after a rewrite. Keep query-only
// navigation on the public URL without leaking dynamic page parameters into the query.
function currentPageHref(router, changes={}) {
  const url = new URL(router.asPath, 'http://localhost')
  return `${url.pathname}${encodeGetParams({ ...Object.fromEntries(url.searchParams), ...changes })}`
}

module.exports = {
  currentPageHref,
  decodeUrlPath,
  encodeUrlFragment,
  home: () => `/`,
  articles: (opts={}) => `/${escapeUsername}/articles${encodeGetParamsWithPage(opts)}`,
  articleComments: (slug, opts={}) => `${articleScope(slug)}/comments${encodeGetParamsWithPage(opts)}`,
  articleDelete: slug => `/${encodeUrlPath(slug)}/${escapeUsername}/delete`,
  articleEdit: slug => `/${encodeUrlPath(slug)}/${escapeUsername}/edit`,
  articleIssues: (slug, opts={}) => `${articleScope(slug)}/discussions${encodeGetParamsWithPage(opts)}`,
  articleNew: (opts={}) => `/${escapeUsername}/new${encodeGetParams(opts)}`,
  articleNewFrom: (slug) => `/${encodeUrlPath(slug)}/${escapeUsername}/new`,
  articlesFollowed: (opts={}) => `/${encodeGetParamsWithPage(opts)}`,
  articleSource: (slug) => `/${encodeUrlPath(slug)}/${escapeUsername}/source`,
  article: slug => `/${encodeUrlPath(slug)}`,
  comments: (opts={}) => `/${escapeUsername}/comments${encodeGetParamsWithPage(opts)}`,
  files: (opts={}) => `/${escapeUsername}/files${encodeGetParamsWithPage(opts)}`,
  dir: (username, dir) => `/${username}/${DIR_PREFIX}${dir ? `/${encodeUrlPath(dir)}` : ''}`,
  host: req => `${req.protocol}://${req.get('host')}`,
  issueComment: (slug, issueNumber, commentNumber) => `${issue(slug, issueNumber)}#${commentIdPrefix}${commentNumber}`,
  issueComments: (slug, number) => `${issue(slug, number)}#${commentsHeaderId}`,
  issueDelete: (slug, number) => `${issue(slug, number)}/delete`,
  issueEdit: (slug, number) => `${issue(slug, number)}/edit`,
  issueNew: (slug) => `/${encodeUrlPath(slug)}/${escapeUsername}/new-discussion`,
  issue,
  issues: (opts={}) => `/${escapeUsername}/discussions${encodeGetParamsWithPage(opts)}`,
  resetPassword: () => `/${escapeUsername}/reset-password`,
  resetPasswordSent: () => `/${escapeUsername}/reset-password-sent`,
  resetPasswordUpdate: () => `/${escapeUsername}/reset-password-update`,
  siteSettings: () => `/${escapeUsername}/site-settings`,
  userEdit: (uid) => `/${uid}/${escapeUsername}/settings`,
  userLogin: () => `/${escapeUsername}/login`,
  userNew: () => `/${escapeUsername}/register`,
  userVerify: (email) => `/${escapeUsername}/verify${encodeGetParams({ email })}`,
  user: (uid) => `/${uid}`,
  userArticles: (uid, opts={}) => `/${uid}/${escapeUsername}/articles${encodeGetParamsWithPage(opts)}`,
  userArticlesChildren: (uid, tagTopicId, opts={}) => `/${uid}${tagTopicId ? `/${encodeUrlPath(tagTopicId)}` : ''}/${escapeUsername}/children${encodeGetParamsWithPage(opts)}`,
  userArticlesIncoming: (uid, tagTopicId, opts={}) => `/${uid}${tagTopicId ? `/${encodeUrlPath(tagTopicId)}` : ''}/${escapeUsername}/incoming${encodeGetParamsWithPage(opts)}`,
  userArticlesTagged: (uid, tagTopicId, opts={}) => `/${uid}${tagTopicId ? `/${encodeUrlPath(tagTopicId)}` : ''}/${escapeUsername}/tagged${encodeGetParamsWithPage(opts)}`,
  userComments: (uid, opts={}) => `/${uid}/${escapeUsername}/comments${encodeGetParamsWithPage(opts)}`,
  userFiles: (uid, opts={}) => `/${uid}/${escapeUsername}/files${encodeGetParamsWithPage(opts)}`,
  userIssues: (uid, opts={}) => `/${uid}/${escapeUsername}/discussions${encodeGetParamsWithPage(opts)}`,
  userFollows: (uid, opts={}) => `/${uid}/${escapeUsername}/follows${encodeGetParamsWithPage(opts)}`,
  userFollowed: (uid, opts={}) => `/${uid}/${escapeUsername}/followed${encodeGetParamsWithPage(opts)}`,
  userLiked: (uid, opts={}) => `/${uid}/${escapeUsername}/liked${encodeGetParamsWithPage(opts)}`,
  userLikedDiscussions: (uid, opts={}) => `/${uid}/${escapeUsername}/liked-discussions${encodeGetParamsWithPage(opts)}`,
  userLikes: (uid, opts={}) => `/${uid}/${escapeUsername}/likes${encodeGetParamsWithPage(opts)}`,
  // TODO https://github.com/ourbigbook/ourbigbook/issues/313
  userLikesDiscussions: (uid, opts={}) => `/${uid}/${escapeUsername}/likes-discussions${encodeGetParamsWithPage(opts)}`,
  userFollowsArticles: (uid, opts={}) => `/${uid}/${escapeUsername}/follows-articles${encodeGetParamsWithPage(opts)}`,
  userFollowsDiscussions: (uid, opts={}) => `/${uid}/${escapeUsername}/follows-discussions${encodeGetParamsWithPage(opts)}`,
  users: (opts={}) => `/${escapeUsername}/users${encodeGetParamsWithPage(opts, { defaultSort: 'score' })}`,
  topic: (id, opts={}) => `/${escapeUsername}/topic/${encodeUrlPath(id)}${encodeGetParamsWithPage(opts, { defaultSort: 'score' })}`,
  topics: (opts={}) => {
    let url
    if (opts.loggedInUser) {
      delete opts.loggedInUser
      url = `/${escapeUsername}/topics`
    } else {
      url = `/`
    }
    return `${url}${encodeGetParamsWithPage(opts, { defaultSort: 'article-count' })}`
  },
}