remove netlify from codebase (#5064)

This commit is contained in:
Eli Kinsey
2023-01-12 03:35:09 -08:00
committed by GitHub
parent 45fce59efe
commit 05761de0ed
26 changed files with 9 additions and 4392 deletions

View File

@@ -44,37 +44,3 @@ jobs:
- name: Push changes
run: |
git push
js_checks:
name: Safe Redirects
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == github.repository
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
- uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: yarn
- name: Run tests
run: yarn test-redirects
- name: Configure Git
run: |
git fetch origin master:master
git config user.name PostHog Bot
git config user.email hey@posthog.com
- name: Add safe redirects
if: ${{ github.base_ref == 'master' }}
run: yarn generate-redirects
- name: Cleanup and commit
if: ${{ github.base_ref == 'master' }}
run: |
rm pr_diff
git add netlify.toml
[ "$(git status -s)" = "" ] && exit 0 || git commit -m "Add safe redirects (JS)"
- name: Push changes
run: |
git push

2
.gitignore vendored
View File

@@ -22,8 +22,6 @@ pr_diff
recipe-server.log
# Local Netlify folder
.netlify
static/fonts/MatterSQItalicVF.woff
static/fonts/MatterSQItalicVF.woff2
static/fonts/MatterSQVF.woff

View File

@@ -111,7 +111,7 @@ Near the bottom of a pull request page, you'll see a box like this:
(Note: This box only appears if you're a member of the PostHog GitHub org - it's not available to the public.)
You can click the _Details_ link on the line that says `netlify/posthog/deploy-preview` to see the preview.
You can click the _Visit Preview_ link in the Vercel bot comment to see the preview.
### Merging changes

View File

@@ -371,27 +371,20 @@ The sidebar is generated from each of the files in `/src/sidebars`.
#### Redirects
Redirects are managed in `netlify.toml` which is located in the root folder.
Redirects are managed in `vercel.json` which is located in the root folder.
To declare a new redirect, open `netlify.toml` and add an entry with the `[[redirects]]` heading:
To declare a new redirect, open `vercel.json` and add an entry to the `redirects` list:
```
[[redirects]]
from = "/docs/integrations/android-integration"
to = "/docs/libraries/android"
{ "source": "/docs/contributing/stack", "destination": "/docs/contribute/stack" }
```
The default HTTP status code is 301, but if you need to define a different status code, it can be changed like this:
The default HTTP status code is 308 (permanent), but if the redirect should be temporary (307), it can be updated like this:
```
[[redirects]]
from = "/docs/integrations/android-integration"
to = "/docs/libraries/android"
status = 302
{ "source": "/docs/contributing/stack", "destination": "/docs/contribute/stack", "permanent": false }
```
> If you ever need to rename a file to get a different slug, a redirect is automatically created with the Safe Redirects action
## Committing changes
It's best to create commits that are focused on one specific area. For example, create one commit for textual changes and another for functional ones. Another example is creating a commit for changes to a section of the handbook and a different commit for updates to the documentation. This helps the pull request review process and also means specific commits can be [cherry picked](https://git-scm.com/docs/git-cherry-pick).
@@ -508,7 +501,7 @@ If you know who you would like to review the pull request, select them in the **
## Preview branch
After a series of checks are run (to ensure nothing in your pull request breaks the website), Netlify will generate a preview link available on the `netlify/posthog/deploy-preview` line. This includes all of your changes so you can preview before your pull request is merged.
After a series of checks are run (to ensure nothing in your pull request breaks the website), Vercel will generate a preview link available in the Vercel bot comment. This includes all of your changes so you can preview before your pull request is merged.
![Preview branch](../../../images/docs/contribute/preview-branch.png)
@@ -520,4 +513,4 @@ To get changes into production, the website deploys automatically from `master`.
#### Acknowledgements
This website is based on [Gatsby](https://gatsbyjs.org) and is hosted with [Netlify](https://www.netlify.com/).
This website is based on [Gatsby](https://gatsbyjs.org) and is hosted with [Vercel](https://vercel.com).

View File

@@ -102,7 +102,7 @@ Submit a PR to [posthog/posthog.com](https://github.com/posthog/posthog.com) wit
- Add a meta description using `description` in the frontmatter section (optional)
- Set the date of the blog post to the intended publishing date in the format `YYYY-MM-DD`. Posts dated [in the future](https://github.com/PostHog/posthog.com/pull/2964) won't display on the site until their specified date, though a build is required day-of in order to publish the post. (The Website & Docs team or other Netlify admins can kick off a manual build.)
- Set the date of the blog post to the intended publishing date in the format `YYYY-MM-DD`. Posts dated [in the future](https://github.com/PostHog/posthog.com/pull/2964) won't display on the site until their specified date, though a build is required day-of in order to publish the post. (The Website & Docs team or other Vercel admins can kick off a manual build.)
- Create an annotation on [app.posthog.com](https://app.posthog.com) for the content to track the effect.

View File

@@ -1,67 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const Busboy = require('busboy')
const request = require('request')
function multipartToAshby(event) {
return new Promise((resolve) => {
const applicationForm = { fieldSubmissions: [] }
const formData = {}
const busboy = Busboy({
headers: event.headers,
})
busboy.on('file', (name, filestream, file) => {
filestream.on('data', (data) => {
formData[name] = {
value: data,
options: {
filename: file.filename,
contentType: null,
},
}
})
})
busboy.on('field', (name, value) => {
if (name === 'jobPostingId') {
formData[name] = value
} else {
applicationForm.fieldSubmissions.push({
path: name,
value,
})
}
})
busboy.on('finish', () => {
formData.applicationForm = JSON.stringify(applicationForm)
resolve(formData)
})
busboy.end(Buffer.from(event.body, 'base64'))
})
}
exports.handler = async (e) => {
const formData = await multipartToAshby(e)
const options = {
method: 'POST',
url: 'https://api.ashbyhq.com/applicationForm.submit',
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Basic ${Buffer.from(`${process.env.ASHBY_API_KEY}:`).toString('base64')}`,
},
formData,
}
const submission = await new Promise((resolve, reject) => {
request(options, function (err, res) {
if (err) throw new Error(err)
resolve(res.body)
})
})
return {
statusCode: 200,
body: JSON.stringify({ submission }),
}
}

View File

@@ -1,142 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fetch = require('node-fetch')
const getGravatar = require('gravatar')
exports.handler = async (e) => {
let { body } = e
if (!body) return { statusCode: 500, body: 'Missing body' }
body = JSON.parse(body)
let avatar
if (body.email) {
const gravatar = getGravatar.url(body.email)
avatar = await fetch(`https:${gravatar}?d=404`).then((res) => (res.ok && `https:${gravatar}`) || '')
}
const message = {
channel: process.env.SLACK_QUESTION_CHANNEL,
ts: body.timestamp,
text: body.question,
username: body.name,
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: `${body.name} on ${body.slug}`,
emoji: true,
},
block_id: 'name_and_slug',
},
{
type: 'header',
text: {
type: 'plain_text',
text: body.subject,
emoji: true,
},
block_id: 'subject',
},
{
type: 'section',
block_id: 'question',
text: {
type: 'mrkdwn',
text: body.question,
},
...(avatar && {
accessory: {
type: 'image',
image_url: avatar,
alt_text: body.name,
},
}),
},
{
type: 'actions',
elements: [
{
type: 'button',
style: 'primary',
text: {
type: 'plain_text',
text: `Publish${body.email ? ' & notify' : ''}`,
emoji: true,
},
value: JSON.stringify({
question: body.question,
name: body.name,
slug: body.slug,
email: body.email,
subject: body.subject,
avatar,
}),
action_id: 'publish-button',
confirm: {
title: {
type: 'plain_text',
text: 'Please confirm',
},
text: {
type: 'mrkdwn',
text: `Clicking confirm will add this thread to the website${
body.email ? ' and send an email to the original poster' : ''
}.`,
},
confirm: {
type: 'plain_text',
text: 'Confirm',
},
deny: {
type: 'plain_text',
text: 'Cancel',
},
},
},
{
type: 'button',
text: {
type: 'plain_text',
text: 'Edit',
emoji: true,
},
value: JSON.stringify({
question: body.question,
name: body.name,
slug: body.slug,
email: body.email,
subject: body.subject,
}),
action_id: 'edit-question-button',
},
{
type: 'button',
text: {
type: 'plain_text',
text: 'Open page',
emoji: true,
},
url: `https://posthog.com${body.slug}`,
action_id: 'open-page-button',
},
],
},
],
}
const slack = await fetch(
body.timestamp ? 'https://slack.com/api/chat.update' : 'https://slack.com/api/chat.postMessage',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.SLACK_API_KEY}`,
},
body: JSON.stringify(message),
}
).then((res) => res.json())
return {
statusCode: 200,
body: JSON.stringify({ timestamp: slack.ts }),
}
}

View File

@@ -1,22 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fetch = require('node-fetch')
exports.handler = async (e) => {
const { queryStringParameters } = e
const companyName = queryStringParameters.name
let data = {}
if (companyName) {
const clearbitData = await fetch(`https://company.clearbit.com/v1/domains/find?name=${companyName}`, {
headers: { Authorization: `Bearer ${process.env.CLEARBIT_API_KEY}` },
}).then((res) => res.json())
const { name, logo } = clearbitData
data = {
name,
logo,
}
}
return {
statusCode: 200,
body: JSON.stringify(data),
}
}

View File

@@ -1,40 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const hubspot = require('@hubspot/api-client')
exports.handler = async (e) => {
let { body } = e
if (!body) return { statusCode: 500, body: 'Missing body' }
body = JSON.parse(body)
const { email, firstName, lastName } = body
if (!email || !firstName || !lastName) {
return {
statusCode: 500,
body: 'Missing required fields',
}
}
const hubspotClient = new hubspot.Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN })
const properties = {
email,
firstname: firstName,
lastname: lastName,
}
const SimplePublicObjectInput = { properties }
try {
const apiResponse = await hubspotClient.crm.contacts.basicApi.create(SimplePublicObjectInput)
console.log(JSON.stringify(apiResponse.body, null, 2))
} catch (e) {
e.message === 'HTTP request failed' ? console.error(JSON.stringify(e.response, null, 2)) : console.error(e)
return {
statusCode: 500,
body: 'HubSpot request failed',
}
}
return {
statusCode: 200,
}
}

View File

@@ -1,22 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fetch = require('node-fetch')
const md5 = require('md5')
exports.handler = async (e) => {
let { body } = e
if (!body) return { statusCode: 500, body: 'Missing body' }
body = JSON.parse(body)
const { email, tag } = body
if (!email || !tag) return { statusCode: 500, body: 'Missing required fields' }
const data = await fetch(`https://us19.api.mailchimp.com/3.0/lists/ef3044881e/members/${md5(email)}/tags`, {
headers: {
Authorization: `Bearer ${process.env.MAILCHIMP_API_KEY}`,
},
method: 'POST',
body: JSON.stringify({ tags: [{ name: tag, status: 'active' }], is_syncing: false }),
})
return {
statusCode: 200,
body: JSON.stringify(data),
}
}

View File

@@ -1,525 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fetch = require('node-fetch')
const queryString = require('query-string')
const formData = require('form-data')
const Mailgun = require('mailgun.js')
const getGravatar = require('gravatar')
const { App, AwsLambdaReceiver } = require('@slack/bolt')
const awsLambdaReceiver = new AwsLambdaReceiver({
signingSecret: process.env.SLACK_SIGNING_SECRET,
})
const app = new App({
signingSecret: process.env.SLACK_SIGNING_SECRET,
token: process.env.SLACK_API_KEY,
receiver: awsLambdaReceiver,
})
app.view('submit-button', async ({ ack, view, client }) => {
await ack()
const {
private_metadata,
state: { values },
} = view
const body = {
slug: null,
name: null,
email: null,
question: null,
subject: null,
}
Object.keys(values).forEach((valueKey) => {
Object.keys(body).forEach((bodyKey) => {
if (values[valueKey][bodyKey]) {
body[bodyKey] = values[valueKey][bodyKey].value
}
})
})
body.timestamp = private_metadata
let avatar
if (body.email) {
const gravatar = getGravatar.url(body.email)
avatar = await fetch(`https:${gravatar}?d=404`).then((res) => (res.ok && `https:${gravatar}`) || '')
}
const message = {
channel: process.env.SLACK_QUESTION_CHANNEL,
ts: body.timestamp,
text: body.question,
username: body.name,
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: `${body.name} on ${body.slug}`,
emoji: true,
},
block_id: 'name_and_slug',
},
{
type: 'header',
text: {
type: 'plain_text',
text: body.subject,
emoji: true,
},
block_id: 'subject',
},
{
type: 'section',
block_id: 'question',
text: {
type: 'mrkdwn',
text: body.question,
},
...(avatar && {
accessory: {
type: 'image',
image_url: avatar,
alt_text: body.name,
},
}),
},
{
type: 'actions',
elements: [
{
type: 'button',
style: 'primary',
text: {
type: 'plain_text',
text: 'Publish',
emoji: true,
},
value: JSON.stringify({
question: body.question,
name: body.name,
slug: body.slug,
email: body.email,
subject: body.subject,
avatar,
}),
action_id: 'publish-button',
confirm: {
title: {
type: 'plain_text',
text: 'Please confirm',
},
text: {
type: 'mrkdwn',
text: 'Clicking confirm will add this thread to the website.',
},
confirm: {
type: 'plain_text',
text: 'Confirm',
},
deny: {
type: 'plain_text',
text: 'Cancel',
},
},
},
{
type: 'button',
text: {
type: 'plain_text',
text: 'Edit',
emoji: true,
},
value: JSON.stringify({
question: body.question,
name: body.name,
slug: body.slug,
email: body.email,
subject: body.subject,
timestamp: body.timestamp,
}),
action_id: 'edit-question-button',
},
{
type: 'button',
text: {
type: 'plain_text',
text: 'Open page',
emoji: true,
},
url: `https://posthog.com${body.slug}`,
action_id: 'open-page-button',
},
],
},
],
}
client.chat.update(message)
})
app.action('edit-question-button', async ({ ack, client, body, logger }) => {
await ack()
try {
const { question, name, email, slug, subject } = JSON.parse(body.actions[0].value)
const result = await client.views.open({
trigger_id: body.trigger_id,
view: {
callback_id: 'submit-button',
private_metadata: body.message.ts,
type: 'modal',
title: {
type: 'plain_text',
text: `Question`,
emoji: true,
},
submit: {
type: 'plain_text',
text: 'Save',
emoji: true,
},
close: {
type: 'plain_text',
text: 'Cancel',
emoji: true,
},
blocks: [
{
type: 'input',
element: {
type: 'plain_text_input',
action_id: 'slug',
initial_value: slug,
},
label: {
type: 'plain_text',
text: 'Slug',
emoji: true,
},
},
{
type: 'input',
element: {
type: 'plain_text_input',
action_id: 'subject',
initial_value: subject,
},
label: {
type: 'plain_text',
text: 'Subject',
emoji: true,
},
},
{
type: 'input',
element: {
type: 'plain_text_input',
action_id: 'name',
initial_value: name,
},
label: {
type: 'plain_text',
text: 'Name',
emoji: true,
},
},
{
type: 'input',
optional: true,
element: {
type: 'plain_text_input',
action_id: 'email',
initial_value: email || '',
},
label: {
type: 'plain_text',
text: 'Email',
emoji: true,
},
},
{
type: 'input',
element: {
type: 'plain_text_input',
multiline: true,
action_id: 'question',
initial_value: question,
},
label: {
type: 'plain_text',
text: 'Question',
emoji: true,
},
},
],
},
})
} catch (error) {
logger.error(error)
}
})
app.action('unpublish-button', async ({ ack, client, body }) => {
await ack()
const { message, actions } = body
const { name, slug, question, avatar, email, subject } = JSON.parse(actions[0].value)
const messageUpdate = {
channel: process.env.SLACK_QUESTION_CHANNEL,
ts: message.ts,
text: question,
username: name,
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: `${name} on ${slug}`,
emoji: true,
},
block_id: 'name_and_slug',
},
{
type: 'header',
text: {
type: 'plain_text',
text: subject,
emoji: true,
},
block_id: 'subject',
},
{
type: 'section',
block_id: 'question',
text: {
type: 'mrkdwn',
text: question,
},
...(avatar && {
accessory: {
type: 'image',
image_url: avatar,
alt_text: name,
},
}),
},
{
type: 'actions',
elements: [
{
type: 'button',
style: 'primary',
text: {
type: 'plain_text',
text: 'Publish',
emoji: true,
},
value: JSON.stringify({
question: question,
name: name,
slug: slug,
email: email,
avatar,
}),
action_id: 'publish-button',
confirm: {
title: {
type: 'plain_text',
text: 'Please confirm',
},
text: {
type: 'mrkdwn',
text: 'Clicking confirm will add this thread to the website.',
},
confirm: {
type: 'plain_text',
text: 'Confirm',
},
deny: {
type: 'plain_text',
text: 'Cancel',
},
},
},
{
type: 'button',
text: {
type: 'plain_text',
text: 'Edit',
emoji: true,
},
value: JSON.stringify({
question: question,
name: name,
slug: slug,
email: email,
}),
action_id: 'edit-question-button',
},
{
type: 'button',
text: {
type: 'plain_text',
text: 'Open page',
emoji: true,
},
url: `https://posthog.com${slug}`,
action_id: 'open-page-button',
},
],
},
],
}
client.chat.update(messageUpdate)
})
app.action('publish-button', async ({ ack, client, body }) => {
await ack()
const { message, actions } = body
const { question, name, slug, email, avatar, subject } = JSON.parse(actions[0].value)
const replies = await client.conversations.replies({
ts: message.ts,
channel: process.env.SLACK_QUESTION_CHANNEL,
})
const answer = replies.messages && replies.messages[1] && replies.messages[1].text
if (answer) {
if (email) {
const mailgun = new Mailgun(formData)
const mg = mailgun.client({
username: 'api',
key: process.env.MAILGUN_API_KEY,
url: 'https://api.eu.mailgun.net',
})
const mailgunData = {
from: 'hey@posthog.com',
to: email,
subject: `Someone answered your question on posthog.com!`,
template: 'question-answered',
'h:X-Mailgun-Variables': JSON.stringify({
question,
answer,
}),
'h:Reply-To': 'hey@posthog.com',
}
await mg.messages.create(process.env.MAILGUN_DOMAIN, mailgunData).catch((err) => console.log(err))
}
const messageUpdate = {
channel: process.env.SLACK_QUESTION_CHANNEL,
ts: message.ts,
text: question,
username: name,
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: `${name} on ${slug}`,
emoji: true,
},
block_id: 'name_and_slug',
},
{
type: 'header',
text: {
type: 'plain_text',
text: subject,
emoji: true,
},
block_id: 'subject',
},
{
type: 'section',
block_id: 'question',
text: {
type: 'mrkdwn',
text: question,
},
...(avatar && {
accessory: {
type: 'image',
image_url: avatar,
alt_text: name,
},
}),
},
{
type: 'section',
block_id: 'published',
text: {
type: 'plain_text',
text: '✅ Published',
emoji: true,
},
},
{
type: 'actions',
elements: [
{
type: 'button',
style: 'danger',
text: {
type: 'plain_text',
text: 'Unpublish',
emoji: true,
},
value: JSON.stringify({
question: question,
name: name,
slug: slug,
email: email,
subject: subject,
avatar,
}),
action_id: 'unpublish-button',
confirm: {
title: {
type: 'plain_text',
text: 'Please confirm',
},
text: {
type: 'mrkdwn',
text: 'Clicking confirm will remove this thread from the website',
},
confirm: {
type: 'plain_text',
text: 'Confirm',
},
deny: {
type: 'plain_text',
text: 'Cancel',
},
},
},
{
type: 'button',
text: {
type: 'plain_text',
text: 'Open page',
emoji: true,
},
url: `https://posthog.com${slug}`,
action_id: 'open-page-button',
},
],
},
],
}
client.chat.update(messageUpdate)
}
})
app.action('open-page-button', async ({ ack }) => {
await ack()
})
exports.handler = async (event, context, callback) => {
const { body } = event
const { payload } = queryString.parse(body)
const { token } = JSON.parse(payload)
if (token !== process.env.SLACK_VERIFICATION_TOKEN) return { statusCode: 500, body: 'Invalid token' }
const handler = await awsLambdaReceiver.start()
return handler(event, context, callback)
}

View File

@@ -1,114 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const queryString = require('query-string')
const { App, AwsLambdaReceiver } = require('@slack/bolt')
const { createClient } = require('@supabase/supabase-js')
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_API_KEY)
const awsLambdaReceiver = new AwsLambdaReceiver({
signingSecret: process.env.SLACK_USERS_SIGNING_SECRET,
})
const app = new App({
signingSecret: process.env.SLACK_USERS_SIGNING_SECRET,
token: process.env.SLACK_USERS_API_KEY,
receiver: awsLambdaReceiver,
})
app.shortcut('publish_thread', async ({ shortcut, ack, client, logger }) => {
try {
await ack()
const user = await client.users.info({ user: shortcut.user.id })
if (!user.user.is_admin) return
const { data, error } = await supabase
.from('Messages')
.select('slack_timestamp, slug')
.eq('slack_timestamp', shortcut.message.ts)
const view = {
trigger_id: shortcut.trigger_id,
view: {
private_metadata: JSON.stringify({
ts: shortcut.message.ts,
channel: shortcut.channel.id,
user: shortcut.user.id,
}),
callback_id: 'submit-db-button',
type: 'modal',
title: {
type: 'plain_text',
text: 'PostHog Q&A',
},
submit: {
type: 'plain_text',
text: 'Publish',
emoji: true,
},
close: {
type: 'plain_text',
text: 'Cancel',
emoji: true,
},
blocks: [
{
type: 'input',
optional: false,
element: {
type: 'plain_text_input',
action_id: 'slug',
},
label: {
type: 'plain_text',
text: 'Slug (eg: /docs/api/insights), separated by commas',
emoji: true,
},
},
],
},
}
if (data.length > 0 && data[0].slug) {
view.view.blocks[0].element.initial_value = data[0].slug
view.view.blocks[0].optional = true
view.view.submit.text = 'Update'
}
await client.views.open(view)
} catch (error) {
logger.error(error)
}
})
app.view('submit-db-button', async ({ ack, view }) => {
await ack()
const {
private_metadata,
state: { values },
} = view
const body = {
slug: null,
}
const { ts, channel, user } = JSON.parse(private_metadata)
Object.keys(values).forEach((valueKey) => {
Object.keys(body).forEach((bodyKey) => {
if (values[valueKey][bodyKey]) {
body[bodyKey] = values[valueKey][bodyKey].value
}
})
})
body.slack_timestamp = ts
body.slack_channel = channel
body.slack_user = user
await supabase.from('Messages').upsert([body])
})
exports.handler = async (event, context, callback) => {
const { body } = event
const { payload } = queryString.parse(body)
const { token } = JSON.parse(payload)
if (token !== process.env.SLACK_USERS_VERIFICATION_TOKEN) return { statusCode: 500, body: 'Invalid token' }
const handler = await awsLambdaReceiver.start()
return handler(event, context, callback)
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,6 @@
"test": "echo \"Error: no test specified\" && exit 1",
"typegen": "kea-typegen write .",
"update-sprite": "svg-sprite -s --symbol-dest src/components/productFeature/images/icons --symbol-sprite sprited-icons.svg src/components/productFeature/images/icons/*.svg",
"generate-redirects": "git diff origin/master $(git branch --show-current) > pr_diff && DEBUG=true RUN_AS_SCRIPT=true node ./scripts/safe_rename.js",
"test-redirects": "jest scripts",
"storybook": "start-storybook -s ./static -p 6006",
"build-storybook": "build-storybook"

View File

@@ -1,147 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs')
const fetch = require('node-fetch')
let DEBUG = false
const log = (...args) => {
if (DEBUG) {
console.log.apply(null, args)
}
}
/** Return date (without time) in ISO format. */
const formatDate = (date) => {
return date.toISOString().slice(0, 10)
}
const redirectText = (from, to) => {
return `
[[redirects]]
from = "${from}"
to = "${to}"
`
}
const redirectWithDateCommentText = (date, redirect) => {
return `
# Added: ${formatDate(date)}${redirect}`
}
// Rules
const samePath = ({ fromPath, toPath }) => {
return fromPath === toPath
}
const mdToMdx = ({ fromPath, toPath }) => {
// # old rule
// # return '.mdx' not in from_paths[i] and '.mdx' in to_paths[i]
return fromPath.endsWith('.md') && toPath.endsWith('.mdx')
}
const redirectExists = ({ redirect, localConfig, remoteConfig }) => {
return localConfig.indexOf(redirect.trim()) !== -1 || remoteConfig.indexOf(redirect.trim()) !== -1
}
const fromDotStar = ({ fromPath }) => {
return fromPath == '(.*)'
}
const isSnippetsRename = ({ fromPath, toPath }) => {
return fromPath.indexOf('/snippets/') !== -1 || toPath.indexOf('/snippets') !== -1
}
const skipRules = {
samePath,
mdToMdx,
redirectExists,
fromDotStar,
isSnippetsRename,
}
// # Load existing remote redirect config
const getRemoteConfig = async () => {
const response = await fetch('https://raw.githubusercontent.com/PostHog/posthog.com/master/netlify.toml')
return await response.text()
}
// # Load existing redirect file to be used to avoid duplicates
const getLocalConfig = async () => {
return await fs.promises.readFile('./netlify.toml', 'utf8')
}
const appendToLocalConfig = async (newRedirects) => {
await fs.promises.appendFile('./netlify.toml', newRedirects)
}
const getRedirects = async ({ gitDiff, localConfig, remoteConfig, debug = false }) => {
DEBUG = debug
log(gitDiff)
const renameFromRegex = new RegExp('rename from contents(.*).md', 'g')
const renameToRegex = new RegExp('rename to contents(.*).md', 'g')
const fromPaths = Array.from(gitDiff.matchAll(renameFromRegex), (m) => m[1])
const toPaths = Array.from(gitDiff.matchAll(renameToRegex), (m) => m[1])
log(fromPaths, toPaths)
const newRedirects = []
if (fromPaths.length > 0 && fromPaths.length === toPaths.length) {
for (let i = 0; i < fromPaths.length; ++i) {
// handle index default directory files. /path/index will become /path
const fromPath = fromPaths[i].replace(/\/index$/, '', fromPaths[i])
const toPath = toPaths[i].replace(/\/index$/, '', toPaths[i])
const redirect = redirectText(fromPath, toPath)
// console.log(redirect)
log(`Testing if redirects are required for: "${fromPath}" to "${toPath}"`)
let skipRedirect = false
for (const [skipRuleName, skipRuleFunction] of Object.entries(skipRules)) {
if (skipRedirect === false) {
skipRedirect = skipRuleFunction({ fromPath, toPath, redirect, localConfig, remoteConfig })
log(`Rule: "${skipRuleName}"', 'skipRedirect?', ${skipRedirect}`)
}
}
if (skipRedirect === false) {
newRedirects.push(redirectWithDateCommentText(new Date(), redirect))
} else {
log('Not including redirect', redirect)
}
}
} else {
log('No path changes found')
}
return newRedirects
}
const main = async () => {
try {
const gitDiff = await fs.promises.readFile('./pr_diff', 'utf8')
const remoteConfig = await getRemoteConfig()
const localConfig = await getLocalConfig()
const debug = process.env.DEBUG
const redirects = await getRedirects({ gitDiff, localConfig, remoteConfig, debug })
if (redirects.length > 0) {
log('Writing', redirects)
await appendToLocalConfig(redirects.join(''))
}
} catch (error) {
console.error(error)
}
}
if (process.env.RUN_AS_SCRIPT === 'true') {
main()
}
module.exports = {
getRedirects,
}

View File

@@ -1,151 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const getRedirects = require('./safe_rename').getRedirects
jest.useFakeTimers().setSystemTime(new Date('2021-08-09').getTime())
const debug = false
const singleRenameGitDiff = `
rename from contents/docs/one.md
rename to contents/docs/two.md`
const multiRenameGitDiff = `
rename from contents/docs/one.md
rename to contents/docs/two.md
rename from contents/docs/cheese.md
rename to contents/docs/mushroom.md
rename from contents/docs/fish.md
rename to contents/docs/monkey.md`
const emptyConfig = ``
const existingRedirectConfig = `
# Added: 2021-08-03
[[redirects]]
from = "/docs/one"
to = "/docs/two"`
test('redirect will be added for single file rename', async () => {
const gitDiff = singleRenameGitDiff
const localConfig = emptyConfig
const remoteConfig = emptyConfig
const redirects = await getRedirects({ gitDiff, localConfig, remoteConfig, debug })
expect(redirects.length).toBe(1)
expect(redirects[0]).toContain('# Added: 2021-08-09')
expect(redirects[0]).toContain('from = "/docs/one"')
expect(redirects[0]).toContain('to = "/docs/two"')
})
test('redirect will be added for multi file rename', async () => {
const gitDiff = multiRenameGitDiff
const localConfig = emptyConfig
const remoteConfig = emptyConfig
const redirects = await getRedirects({ gitDiff, localConfig, remoteConfig, debug })
expect(redirects.length).toBe(3)
expect(redirects[0]).toContain('# Added: 2021-08-09')
expect(redirects[0]).toContain('from = "/docs/one"')
expect(redirects[0]).toContain('to = "/docs/two"')
expect(redirects[1]).toContain('# Added: 2021-08-09')
expect(redirects[1]).toContain('from = "/docs/cheese"')
expect(redirects[1]).toContain('to = "/docs/mushroom"')
expect(redirects[2]).toContain('from = "/docs/fish"')
expect(redirects[2]).toContain('to = "/docs/monkey"')
})
test('redirect will only be added for non-existing redirects in multi file rename', async () => {
const gitDiff = multiRenameGitDiff
const localConfig = emptyConfig
const remoteConfig = existingRedirectConfig
const redirects = await getRedirects({ gitDiff, localConfig, remoteConfig, debug })
expect(redirects.length).toBe(2)
expect(redirects[0]).toContain('from = "/docs/cheese"')
expect(redirects[0]).toContain('to = "/docs/mushroom"')
expect(redirects[1]).toContain('from = "/docs/fish"')
expect(redirects[1]).toContain('to = "/docs/monkey"')
})
test('redirect will not be added if it already exists in the remote config', async () => {
const gitDiff = singleRenameGitDiff
const localConfig = emptyConfig
const remoteConfig = existingRedirectConfig
const redirects = await getRedirects({ gitDiff, localConfig, remoteConfig, debug })
expect(redirects.length).toBe(0)
})
test('redirect will not be added if it already exists in the local config', async () => {
const gitDiff = singleRenameGitDiff
const localConfig = existingRedirectConfig
const remoteConfig = emptyConfig
const redirects = await getRedirects({ gitDiff, localConfig, remoteConfig, debug })
expect(redirects.length).toBe(0)
})
test('redirect will not be added if rename from /name to /name/index.mdx', async () => {
const gitDiff = `
rename from contents/docs/filename
rename to contents/docs/name/index.mdx`
const localConfig = emptyConfig
const remoteConfig = emptyConfig
const redirects = await getRedirects({ gitDiff, localConfig, remoteConfig, debug })
expect(redirects.length).toBe(0)
})
test('redirect will not be added if rename from /name to /name/index.md', async () => {
const gitDiff = `
rename from contents/docs/name
rename to contents/docs/name/index.md`
const localConfig = emptyConfig
const remoteConfig = emptyConfig
const redirects = await getRedirects({ gitDiff, localConfig, remoteConfig, debug })
expect(redirects.length).toBe(0)
})
test('redirect will not be added if rename from .md to .mdx', async () => {
const gitDiff = `
rename from contents/docs/filename.md
rename to contents/docs/filename.mdx`
const localConfig = emptyConfig
const remoteConfig = emptyConfig
const redirects = await getRedirects({ gitDiff, localConfig, remoteConfig, debug })
expect(redirects.length).toBe(0)
})
test('redirect will not be added when renaming snippets', async () => {
const gitDiff = `
rename from contents/docs/snippets/js/capture.mdx
rename to contents/docs/snippets/js/special-capture.mdx`
const localConfig = emptyConfig
const remoteConfig = emptyConfig
const redirects = await getRedirects({ gitDiff, localConfig, remoteConfig, debug })
expect(redirects.length).toBe(0)
})

View File

@@ -1,79 +0,0 @@
import { useLocation } from '@reach/router'
import { Formik } from 'formik'
import React, { useState } from 'react'
import usePostHog from '../../hooks/usePostHog'
import AskQuestion from './AskQuestion'
import Avatar from './Avatar'
import QuestionSubmitted from './QuestionSubmitted'
export default function AskAQuestion() {
const location = useLocation()
const posthog = usePostHog()
const [timestamp, setTimestamp] = useState(null)
const [emailSubmitted, setEmailSubmitted] = useState(false)
return (
<div className="mt-10">
<h4>Ask a question</h4>
<div className="flex items-start space-x-4">
<Avatar />
<div className="w-full max-w-[405px]">
<Formik
isInitialValid={false}
initialValues={{ name: '', question: '', email: '', subject: '', 'mary-chain': '' }}
validate={(values) => {
const errors = {}
if (!values.name) {
errors.name = 'Required'
}
if (!values.subject) {
errors.subject = 'Required'
}
if (!values.question) {
errors.question = 'Required'
}
if (!values.email) {
errors.email = 'Required'
}
return errors
}}
onSubmit={(values, { setSubmitting, resetForm }) => {
if (values['mary-chain']) return
setSubmitting(true)
const body = JSON.stringify({
...values,
slug: location.pathname,
timestamp,
})
fetch('/.netlify/functions/ask-a-question', { method: 'POST', body })
.then((res) => res.json())
.then((data) => {
posthog.capture('Question asked')
setTimestamp(data.timestamp)
setEmailSubmitted(true)
setSubmitting(false)
})
}}
>
{({ isSubmitting, isValid, values, setFieldValue, submitForm }) => {
return !timestamp ? (
<AskQuestion
submitForm={submitForm}
setFieldValue={setFieldValue}
loading={isSubmitting}
isValid={isValid}
/>
) : (
<QuestionSubmitted
loading={isSubmitting}
values={values}
emailSubmitted={emailSubmitted}
isValid={isValid}
/>
)
}}
</Formik>
</div>
</div>
</div>
)
}

View File

@@ -1,80 +0,0 @@
import { Field } from 'formik'
import React from 'react'
import Button from './Button'
import RichText from './RichText'
export default function AskQuestion({ isValid, loading, setFieldValue, submitForm }) {
return (
<>
<Field
className="bg-white dark:bg-gray-accent-light dark:text-primary py-2 px-4 text-lg rounded-md w-full shadow-md"
type="text"
name="name"
placeholder="Full name"
/>
<Field
className="bg-white dark:bg-gray-accent-light dark:text-primary py-2 px-4 text-lg rounded-md mt-2 w-full shadow-md"
type="email"
name="email"
placeholder="Email"
/>
<Field
className="bg-white dark:bg-gray-accent-light dark:text-primary py-2 px-4 text-lg rounded-md mt-2 w-full shadow-md"
type="text"
name="subject"
placeholder="Subject"
/>
<Field className="mary-chain" type="text" placeholder="Ignore if human" name="mary-chain" />
<RichText setFieldValue={setFieldValue} />
<div className="flex items-center justify-between">
<Button onClick={() => submitForm()} loading={loading} type="submit" disabled={!isValid}>
Submit
</Button>
<p className="m-0 flex items-center space-x-2">
<span className="text-[13px] opacity-30 font-semibold">Supports</span>
<svg
className="text-black dark:text-gray-accent-light"
width="30"
height="18"
viewBox="0 0 30 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_3781_82340)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.11791 1.40625C1.72802 1.40625 1.41194 1.72105 1.41194 2.10938V15.8906C1.41194 16.2789 1.72802 16.5938 2.11791 16.5938H27.2505C27.6404 16.5938 27.9565 16.2789 27.9565 15.8906V2.10938C27.9565 1.72105 27.6404 1.40625 27.2505 1.40625H2.11791ZM0 2.10938C0 0.944399 0.948223 0 2.11791 0H27.2505C28.4202 0 29.3684 0.944399 29.3684 2.10938V15.8906C29.3684 17.0556 28.4202 18 27.2505 18H2.11791C0.948223 18 0 17.0556 0 15.8906V2.10938Z"
fill="currentColor"
/>
<path
d="M4.23584 13.7812V4.21875H7.05973L9.88361 7.73438L12.7075 4.21875H15.5314V13.7812H12.7075V8.29688L9.88361 11.8125L7.05973 8.29688V13.7812H4.23584ZM21.8851 13.7812L17.6493 9.14062H20.4732V4.21875H23.2971V9.14062H26.121L21.8851 13.7812Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_3781_82340">
<rect width="29.3684" height="18" fill="white" />
</clipPath>
</defs>
</svg>
<a
className="text-black dark:text-white hover:text-black dark:hover:text-white"
href="https://www.markdownguide.org/basic-syntax/"
target="_blank"
rel="noopener noreferrer"
>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.2">
<path
d="M5.99988 0.283127C4.48392 0.283127 3.02988 0.885467 1.95732 1.95749C0.885287 3.02999 0.282959 4.48409 0.282959 6.00005C0.282959 7.51601 0.885299 8.97005 1.95732 10.0426C3.02982 11.1146 4.48392 11.717 5.99988 11.717C7.51584 11.717 8.96988 11.1146 10.0424 10.0426C11.1145 8.97011 11.7168 7.51601 11.7168 6.00005C11.7168 4.48409 11.1145 3.03005 10.0424 1.95749C8.96994 0.885455 7.51584 0.283127 5.99988 0.283127ZM5.99988 2.29217C6.19769 2.29217 6.38754 2.37092 6.52769 2.5106C6.66785 2.65076 6.74659 2.8406 6.74612 3.03888C6.74659 3.23669 6.66784 3.42654 6.52769 3.56669C6.38753 3.70638 6.19769 3.78512 5.99988 3.78512C5.80207 3.78512 5.61222 3.70637 5.47207 3.56669C5.33191 3.42653 5.25317 3.23669 5.25363 3.03888C5.25317 2.8406 5.33192 2.65075 5.47207 2.5106C5.61223 2.37091 5.80207 2.29217 5.99988 2.29217ZM5.99988 4.71893C6.39269 4.71893 6.70862 5.03486 6.70862 5.42767V8.99911C6.70862 9.39192 6.39269 9.70786 5.99988 9.70786C5.60707 9.70786 5.29113 9.39192 5.29113 8.99911V5.42767C5.29113 5.03486 5.60707 4.71893 5.99988 4.71893Z"
fill="currentColor"
/>
</g>
</svg>
</a>
</p>
</div>
</>
)
}

View File

@@ -1,54 +0,0 @@
import React, { useState } from 'react'
import { Check } from 'components/Icons/Icons'
import GitHubButton from 'react-github-btn'
import emailSaved from './email-saved.svg'
import Button from './Button'
import addToMailchimp from 'gatsby-plugin-mailchimp'
export default function EmailSubmitted({ email }) {
const [subscribed, setSubscribed] = useState(false)
const handleSubscribe = async () => {
await addToMailchimp(email)
setSubscribed(true)
}
return (
<div className="text-center">
{subscribed ? (
<>
<p className="flex justify-center items-center space-x-1 font-semibold text-[#43AF79] m-0">
<span className=" w-[24px] h-[24px] bg-[#43AF79] rounded-full flex justify-center items-center">
<Check className="w-[12px] h-[12px] text-white" />
</span>
<span>You're subscribed!</span>
</p>
<h6 className="mt-2 mb-3">
Be sure to check us out on <a href="https://github.com/PostHog/posthog">GitHub</a>
</h6>
<p className="m-0 flex justify-center items-centertext-white font-semibold">
<GitHubButton
className="text-white hover:text-white"
href="https://github.com/posthog/posthog"
data-size="large"
data-show-count="true"
aria-label="Star posthog/posthog on GitHub"
>
Star
</GitHubButton>
</p>
</>
) : (
<>
<div className="bg-tan dark:bg-primary p-4 rounded-sm mb-4">
<h6 className="m-0">Well email you.</h6>
<p className="text-[14px] opacity-50 m-0">We typically answer in 1-2 days.</p>
<img className="mx-auto mt-2" src={emailSaved} />
</div>
<p className="text-[14px] opacity-50 m-0">While we have you...</p>
<h6 className="m-0">Care to receive our email updates?</h6>
<Button onClick={handleSubscribe}>Sure, subscribe me to the newsletter!</Button>
</>
)}
</div>
)
}

View File

@@ -1,78 +0,0 @@
import { MDXProvider } from '@mdx-js/react'
import { Blockquote } from 'components/BlockQuote'
import { MdxCodeBlock } from 'components/CodeBlock'
import { InlineCode } from 'components/InlineCode'
import Link from 'components/Link'
import { ZoomImage } from 'components/ZoomImage'
import { MDXRenderer } from 'gatsby-plugin-mdx'
import React from 'react'
import Avatar from './Avatar'
export const Days = ({ ts, url }) => {
const days = Number(ts)
return (
<Link
to={url}
className="font-normal ml-2 text-black hover:text-black dark:text-white dark:hover:text-white opacity-30"
>
{ts <= 0 ? 'Today' : `${ts} day${days === 1 ? '' : 's'} ago`}
</Link>
)
}
export const Reply = ({ avatar, name, childMdx, teamMember, ts, parentId, id, className = '' }) => {
const components = {
inlineCode: InlineCode,
blockquote: Blockquote,
pre: MdxCodeBlock,
img: ZoomImage,
}
return (
<div id={id} className={`py-4 rounded-md w-full mt-3 ${className}`}>
<div className="flex space-x-4 items-start">
<Avatar image={teamMember?.frontmatter?.headshot || avatar} />
<div className="min-w-0">
<p className="m-0 text-[14px] font-semibold text-black dark:text-white">
<span>{teamMember?.frontmatter?.name || name}</span>
<span className="opacity-50">, {teamMember?.frontmatter?.jobTitle || 'Contributor'}</span>
<Days ts={ts} url={`/questions/${parentId}#${id}`} />
</p>
<div className="my-3">
<MDXProvider components={components}>
<MDXRenderer>{childMdx.body}</MDXRenderer>
</MDXProvider>
</div>
</div>
</div>
</div>
)
}
export default function Question({ question, id }) {
const components = {
inlineCode: InlineCode,
blockquote: Blockquote,
pre: CodeBlock,
img: ZoomImage,
}
const { avatar, childMdx, name, ts, subject } = question[0]
return (
<div className="flex items-start space-x-4 w-full">
<Avatar image={avatar} />
<div className="flex-grow max-w-[405px]">
{subject && <h3 className="m-0 mb-1 text-[15px]">{subject}</h3>}
<div>
<MDXProvider components={components}>
<MDXRenderer>{childMdx.body}</MDXRenderer>
</MDXProvider>
</div>
<p className="text-[14px] mb-3 text-black dark:text-white">
<span className="font-semibold opacity-50">by {name}</span>{' '}
<Days ts={ts} url={`/questions/${id}`} />
</p>
{question.length > 1 &&
question.slice(1).map((reply, index) => <Reply key={index} parentId={id} {...reply} />)}
</div>
</div>
)
}

View File

@@ -1,27 +0,0 @@
import { Check } from 'components/Icons/Icons'
import React from 'react'
import ReactMarkdown from 'react-markdown'
import EmailSubmitted from './EmailSubmitted'
import SubmitEmail from './SubmitEmail'
export default function QuestionSubmitted({ values, emailSubmitted, isValid, loading }) {
return (
<div>
<ReactMarkdown>{values.question}</ReactMarkdown>
<p className="text-[14px] font-semibold opacity-50">by {values.name}</p>
<p className="flex items-center space-x-1 font-semibold text-[#43AF79]">
<span className=" w-[24px] h-[24px] bg-[#43AF79] rounded-full flex justify-center items-center">
<Check className="w-[12px] h-[12px] text-white" />
</span>
<span>Question sent. Answer will be posted here.</span>
</p>
<div className="p-6 bg-white dark:bg-gray-accent-dark rounded-[10px]">
{emailSubmitted ? (
<EmailSubmitted email={values.email} />
) : (
<SubmitEmail loading={loading} isValid={isValid} />
)}
</div>
</div>
)
}

View File

@@ -1,128 +0,0 @@
import MDEditor from '@uiw/react-md-editor'
import React, { useEffect } from 'react'
import rehypeSanitize from 'rehype-sanitize'
const bold = {
name: 'bold',
keyCommand: 'bold',
buttonProps: { 'aria-label': 'Bold', tabIndex: '-1' },
icon: (
<svg width="11" height="13" viewBox="0 0 11 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M0.394742 13H5.65074C8.31474 13 10.1867 11.632 10.1867 9.238C10.1867 7.6 9.21474 6.574 8.08074 6.124C8.80074 5.728 9.53874 4.828 9.53874 3.64C9.53874 1.534 7.79274 0.399999 5.52474 0.399999H0.394742V13ZM3.23874 10.552V7.546H5.41674C6.87474 7.546 7.50474 8.086 7.50474 9.076C7.50474 10.048 6.87474 10.552 5.41674 10.552H3.23874ZM3.23874 5.17V2.632H5.23674C6.42474 2.632 6.92874 3.118 6.92874 3.91C6.92874 4.684 6.42474 5.17 5.29074 5.17H3.23874Z"
fill="black"
/>
</svg>
),
execute: (state, api) => {
let modifyText = `**${state.selectedText}**`
if (!state.selectedText) {
modifyText = `****`
}
api.replaceSelection(modifyText)
},
}
const italic = {
name: 'italic',
keyCommand: 'italic',
buttonProps: { 'aria-label': 'Italicize', tabIndex: '-1' },
icon: (
<svg width="4" height="13" viewBox="0 0 4 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M0.0360938 13H1.56609L3.00609 3.928H1.47609L0.0360938 13ZM1.63809 2.2H3.40209L3.70809 0.256H1.94409L1.63809 2.2Z"
fill="black"
/>
</svg>
),
execute: (state, api) => {
let modifyText = `*${state.selectedText}*`
if (!state.selectedText) {
modifyText = `**`
}
api.replaceSelection(modifyText)
},
}
const code = {
name: 'code',
keyCommand: 'code',
buttonProps: { 'aria-label': 'Code', tabIndex: '-1' },
icon: (
<svg width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.80395 1.35631C5.80395 1.06647 5.69075 0.794129 5.48513 0.588497C5.08756 0.192017 4.34818 0.191471 3.95227 0.589044L0.31773 4.22415C0.115934 4.42594 0 4.70485 0 4.99087C0 5.27634 0.115937 5.5558 0.31773 5.75759L3.95339 9.39326C4.15792 9.59723 4.43026 9.70989 4.71957 9.70989C5.00777 9.70989 5.27957 9.59724 5.48575 9.39216C5.69082 9.18764 5.80402 8.91473 5.80402 8.62544C5.80402 8.33614 5.69082 8.06325 5.48575 7.85871L2.61855 4.99095L5.48533 2.12375C5.69096 1.91923 5.80415 1.64688 5.80415 1.35648L5.80395 1.35631Z"
fill="black"
/>
<path
d="M13.6821 4.22397L10.047 0.588315C9.64939 0.19129 8.91274 0.190183 8.51298 0.589409C8.30846 0.793935 8.1958 1.06684 8.1958 1.35559C8.1958 1.64597 8.309 1.91832 8.51298 2.12231L11.3813 4.99063L8.51298 7.85895C8.30846 8.06347 8.1958 8.33638 8.1958 8.62513C8.1958 8.91388 8.30846 9.18676 8.51408 9.39294C8.71915 9.59692 8.99205 9.70958 9.28025 9.70958C9.56846 9.70958 9.8408 9.59692 10.0464 9.3924L13.6815 5.7573C13.8833 5.5555 13.9993 5.27605 13.9993 4.99057C13.9993 4.70455 13.8839 4.42565 13.6821 4.22385L13.6821 4.22397Z"
fill="black"
/>
</svg>
),
execute: (state, api) => {
let modifyText = `\`\`\`\n${state.selectedText}\n\`\`\``
if (!state.selectedText) {
modifyText = `\`\`\`\`\`\``
}
api.replaceSelection(modifyText)
},
}
const link = {
name: 'link',
keyCommand: 'link',
buttonProps: { 'aria-label': 'Link', tabIndex: '-1' },
icon: (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_3781_82365)">
<path
d="M12.9069 1.09374C11.449 -0.364215 9.08481 -0.364215 7.62741 1.09374L5.54435 3.17624C5.70951 3.1582 5.87576 3.15054 6.0431 3.15054C6.57028 3.15054 7.08271 3.23421 7.5684 3.39554L8.74856 2.21538C9.15379 1.80961 9.69301 1.58648 10.2667 1.58648C10.8398 1.58648 11.3791 1.80961 11.7849 2.21538C12.1901 2.62061 12.4132 3.15875 12.4132 3.73298C12.4132 4.30612 12.1901 4.84533 11.7849 5.25058L9.47544 7.56002C9.06966 7.9658 8.53044 8.18893 7.95728 8.18893C7.38305 8.18893 6.84494 7.9658 6.43912 7.56002C6.24169 7.3637 6.08803 7.13455 5.98193 6.88518C5.71889 6.89995 5.4728 7.00932 5.28466 7.19691L4.66943 7.81268C4.83787 8.12441 5.05389 8.41862 5.31693 8.68275C6.77489 10.1407 9.13907 10.1407 10.597 8.68275L12.907 6.37219C14.3644 4.91479 14.3644 2.55117 12.907 1.09377L12.9069 1.09374Z"
fill="black"
/>
<path
d="M7.98178 10.8489C7.4535 10.8489 6.93614 10.7636 6.43954 10.5951L5.25117 11.7835C4.84594 12.1893 4.30727 12.4124 3.73357 12.4124C3.16044 12.4124 2.62178 12.1893 2.21597 11.7835C1.8102 11.3783 1.58707 10.8396 1.58707 10.2659C1.58707 9.69275 1.8102 9.15354 2.21597 8.74772L4.52541 6.43828C4.93119 6.03305 5.46932 5.81047 6.04301 5.81047C6.61724 5.81047 7.15536 6.0336 7.56117 6.43828C7.7586 6.63571 7.91281 6.86485 8.01945 7.11421C8.28359 7.10054 8.53023 6.99007 8.71781 6.80249L9.33195 6.18726C9.16351 5.87445 8.94695 5.58078 8.68336 5.31663C7.2254 3.85867 4.86122 3.85867 3.40382 5.31663L1.09438 7.62719C-0.364142 9.08515 -0.364142 11.4482 1.09438 12.9067C2.55234 14.3647 4.9154 14.3647 6.37336 12.9067L8.45306 10.827C8.29774 10.8412 8.14133 10.8495 7.98329 10.8495L7.98178 10.8489Z"
fill="black"
/>
</g>
<defs>
<clipPath id="clip0_3781_82365">
<rect width="14" height="14" fill="white" />
</clipPath>
</defs>
</svg>
),
execute: (state, api) => {
let modifyText = `[${state.selectedText}]()`
if (!state.selectedText) {
modifyText = `[]()`
}
api.replaceSelection(modifyText)
},
}
export default function RichText({ setFieldValue }) {
const [value, setValue] = React.useState('')
useEffect(() => {
setFieldValue('question', value)
}, [value])
return (
<div className="container">
<MDEditor
placeholder="Type more details..."
visiableDragbar={false}
commands={[bold, italic, code, link]}
commandsFilter={(cmd) =>
cmd && /(fullscreen|divider|preview|live|edit)/.test(cmd.name || cmd.keyCommand) ? false : cmd
}
preview={'edit'}
value={value}
onChange={setValue}
previewOptions={{
rehypePlugins: [[rehypeSanitize]],
}}
/>
</div>
)
}

View File

@@ -1,27 +0,0 @@
import React from 'react'
import { Form, Field } from 'formik'
import Button from './Button'
export default function SubmitEmail({ isValid, loading }) {
return (
<>
<h4 className="text-[20px] m-0">Get your answer by email</h4>
<p>No need to constantly refresh this page for updates!</p>
<Form className="grid m-0">
<Field
className="bg-gray-accent-light py-2 px-4 text-lg rounded-md w-full dark:text-primary"
type="email"
name="email"
placeholder="Email"
/>
<Button loading={loading} disabled={!isValid} type="submit">
Send email updates
</Button>
</Form>
<p className="text-[14px] opacity-50 text-center m-0">
Well only email you when an answer is posted. We dont share emails, and if you use Gravatar, well
include your photo next to your question.
</p>
</>
)
}

View File

@@ -1,30 +0,0 @@
import React from 'react'
import { Check } from 'components/Icons/Icons'
import GitHubButton from 'react-github-btn'
export default function Subscribed() {
return (
<>
<p className="flex justify-center items-center space-x-1 font-semibold text-[#43AF79] m-0">
<span className=" w-[24px] h-[24px] bg-[#43AF79] rounded-full flex justify-center items-center">
<Check className="w-[12px] h-[12px] text-white" />
</span>
<span>You're subscribed!</span>
</p>
<h6 className="mt-2 mb-3">
Be sure to check us out on <a href="https://github.com/PostHog/posthog">Github.com</a>
</h6>
<p className="m-0 flex justify-center items-center text-white font-semibold h-[28px] w-[105px]">
<GitHubButton
className="text-white hover:text-white"
href="https://github.com/posthog/posthog"
data-size="large"
data-show-count="true"
aria-label="Star posthog/posthog on GitHub"
>
Star
</GitHubButton>
</p>
</>
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB