remove studio
19
.gitignore
vendored
|
@ -1,2 +1,21 @@
|
||||||
|
# build output
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# generated types
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
# macOS-specific files
|
# macOS-specific files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
21
frontend/.gitignore
vendored
|
@ -1,21 +0,0 @@
|
||||||
# build output
|
|
||||||
dist/
|
|
||||||
|
|
||||||
# generated types
|
|
||||||
.astro/
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# logs
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
|
|
||||||
# environment variables
|
|
||||||
.env
|
|
||||||
.env.production
|
|
||||||
|
|
||||||
# macOS-specific files
|
|
||||||
.DS_Store
|
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 511 KiB After Width: | Height: | Size: 511 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 867 B After Width: | Height: | Size: 867 B |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 402 KiB After Width: | Height: | Size: 402 KiB |
Before Width: | Height: | Size: 476 KiB After Width: | Height: | Size: 476 KiB |
Before Width: | Height: | Size: 559 KiB After Width: | Height: | Size: 559 KiB |
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 193 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 453 KiB After Width: | Height: | Size: 453 KiB |
0
frontend/src/env.d.ts → src/env.d.ts
vendored
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "@sanity/eslint-config-studio"
|
|
||||||
}
|
|
29
studio/.gitignore
vendored
|
@ -1,29 +0,0 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# Dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# Compiled Sanity Studio
|
|
||||||
/dist
|
|
||||||
|
|
||||||
# Temporary Sanity runtime, generated by the CLI on every dev server start
|
|
||||||
/.sanity
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
/logs
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Coverage directory used by testing tools
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# Misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# Typescript
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Dotenv and similar local-only files
|
|
||||||
*.local
|
|
|
@ -1,9 +0,0 @@
|
||||||
# Sanity Clean Content Studio
|
|
||||||
|
|
||||||
Congratulations, you have now installed the Sanity Content Studio, an open source real-time content editing environment connected to the Sanity backend.
|
|
||||||
|
|
||||||
Now you can do the following things:
|
|
||||||
|
|
||||||
- [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme)
|
|
||||||
- [Join the community Slack](https://slack.sanity.io/?utm_source=readme)
|
|
||||||
- [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme)
|
|
|
@ -1,35 +0,0 @@
|
||||||
import {useEffect, useState} from 'react'
|
|
||||||
import {DocumentActionComponent, PortableTextBlock, useDocumentOperation} from 'sanity'
|
|
||||||
import {toPlainText} from '@portabletext/toolkit'
|
|
||||||
|
|
||||||
export default function customPublishAction(originalPublishAction: DocumentActionComponent) {
|
|
||||||
const BetterAction: DocumentActionComponent = (props) => {
|
|
||||||
const {patch, publish} = useDocumentOperation(props.id, props.type)
|
|
||||||
const [isPublishing, setIsPublishing] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isPublishing && !props.draft) {
|
|
||||||
setIsPublishing(false)
|
|
||||||
}
|
|
||||||
}, [props.draft, isPublishing])
|
|
||||||
|
|
||||||
if (props.type !== 'blogPost') return originalPublishAction(props)
|
|
||||||
|
|
||||||
const content = (props.draft?.content ?? props.published?.content) as
|
|
||||||
| PortableTextBlock[]
|
|
||||||
| undefined
|
|
||||||
const readTimeInMinutes = content ? Math.ceil(toPlainText(content).length / 5 / 180) : 0
|
|
||||||
|
|
||||||
return {
|
|
||||||
disabled: !!publish.disabled,
|
|
||||||
label: isPublishing ? 'Publishing…' : 'Update & Publish',
|
|
||||||
onHandle: () => {
|
|
||||||
setIsPublishing(true)
|
|
||||||
props.type === 'blogPost' && patch.execute([{set: {readTime: `${readTimeInMinutes} min`}}])
|
|
||||||
publish.execute()
|
|
||||||
props.onComplete()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return BetterAction
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"name": "personal-website",
|
|
||||||
"private": true,
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "package.json",
|
|
||||||
"license": "UNLICENSED",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "sanity dev",
|
|
||||||
"start": "sanity start",
|
|
||||||
"build": "sanity build && echo cms.filipesm.eu > ./dist/CNAME",
|
|
||||||
"deploy": "pnpm build && surge dist"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"sanity"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"@portabletext/toolkit": "^2.0.10",
|
|
||||||
"@sanity/document-internationalization": "^2.0.1",
|
|
||||||
"@sanity/icons": "^2.6.0",
|
|
||||||
"@sanity/vision": "^3.18.1",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-is": "^18.2.0",
|
|
||||||
"sanity": "^3.18.1",
|
|
||||||
"sanity-plugin-asset-source-unsplash": "^1.1.1",
|
|
||||||
"sanity-plugin-media": "^2.2.2",
|
|
||||||
"styled-components": "^6.1.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@sanity/eslint-config-studio": "^3.0.1",
|
|
||||||
"@types/react": "^18.2.31",
|
|
||||||
"@types/styled-components": "^5.1.29",
|
|
||||||
"eslint": "^8.52.0",
|
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"typescript": "^5.2.2"
|
|
||||||
},
|
|
||||||
"prettier": {
|
|
||||||
"semi": false,
|
|
||||||
"printWidth": 100,
|
|
||||||
"bracketSpacing": false,
|
|
||||||
"singleQuote": true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
import {defineCliConfig} from 'sanity/cli'
|
|
||||||
|
|
||||||
export default defineCliConfig({
|
|
||||||
api: {
|
|
||||||
projectId: 'tzamgyrm',
|
|
||||||
dataset: 'production'
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,45 +0,0 @@
|
||||||
import {defineConfig} from 'sanity'
|
|
||||||
import {deskTool} from 'sanity/desk'
|
|
||||||
import {media} from 'sanity-plugin-media'
|
|
||||||
import {visionTool} from '@sanity/vision'
|
|
||||||
import {schemaTypes} from './schemas'
|
|
||||||
import customPublishAction from './documentActions/blogPostPublishAction'
|
|
||||||
import {unsplashImageAsset} from 'sanity-plugin-asset-source-unsplash'
|
|
||||||
import {
|
|
||||||
documentInternationalization,
|
|
||||||
type PluginConfig as DocumentInternationalizationPluginConfig,
|
|
||||||
} from '@sanity/document-internationalization'
|
|
||||||
|
|
||||||
const documentInternationalizationConfig: DocumentInternationalizationPluginConfig = {
|
|
||||||
supportedLanguages: [
|
|
||||||
{id: 'pt_PT', title: 'Português'},
|
|
||||||
{id: 'en_EN', title: 'English'},
|
|
||||||
],
|
|
||||||
schemaTypes: ['blogPost', 'libraryItem'],
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
name: 'personal-webiste',
|
|
||||||
title: 'Personal website',
|
|
||||||
|
|
||||||
projectId: 'tzamgyrm',
|
|
||||||
dataset: 'production',
|
|
||||||
|
|
||||||
plugins: [
|
|
||||||
deskTool(),
|
|
||||||
documentInternationalization(documentInternationalizationConfig),
|
|
||||||
visionTool(),
|
|
||||||
media(),
|
|
||||||
unsplashImageAsset(),
|
|
||||||
],
|
|
||||||
schema: {
|
|
||||||
types: schemaTypes,
|
|
||||||
},
|
|
||||||
|
|
||||||
document: {
|
|
||||||
actions: (originalActions) =>
|
|
||||||
originalActions.map((originalAction) =>
|
|
||||||
originalAction.action === 'publish' ? customPublishAction(originalAction) : originalAction,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -1,235 +0,0 @@
|
||||||
import {DocumentTextIcon, LinkIcon, InsertBelowIcon, BookIcon} from '@sanity/icons'
|
|
||||||
import {defineType} from 'sanity'
|
|
||||||
|
|
||||||
const blogPost = defineType({
|
|
||||||
name: 'blogPost',
|
|
||||||
title: 'Blog posts',
|
|
||||||
type: 'document',
|
|
||||||
icon: DocumentTextIcon,
|
|
||||||
fieldsets: [
|
|
||||||
{
|
|
||||||
name: 'metadata',
|
|
||||||
title: 'Social media and SEO',
|
|
||||||
description:
|
|
||||||
'Data about the article for SEO, social media previews, etc. This is very important for reach and brand awareness since it will be the first contact many people will have with our brand and content!',
|
|
||||||
options: {
|
|
||||||
collapsible: true,
|
|
||||||
collapsed: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'title',
|
|
||||||
title: 'Title',
|
|
||||||
type: 'string',
|
|
||||||
validation: (r) => r.required(),
|
|
||||||
description:
|
|
||||||
"The title of the article. Not only will it be used in the article page and list, but also in social previews and other websites, if you don't want to costumise it.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'subtitle',
|
|
||||||
title: 'Subtitle',
|
|
||||||
type: 'array',
|
|
||||||
of: [{type: 'block', styles: [{title: 'Normal', value: 'normal'}]}],
|
|
||||||
description: 'The subtitle of the article. This will show beneath the title, if you use it.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'slug',
|
|
||||||
title: 'Slug',
|
|
||||||
type: 'slug',
|
|
||||||
options: {
|
|
||||||
source: 'title',
|
|
||||||
maxLength: 96,
|
|
||||||
},
|
|
||||||
validation: (r) => r.required(),
|
|
||||||
description:
|
|
||||||
'This will be the URL of this article. E.g. "article-one" will be available at URL "/blog/article-one".',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'headerImage',
|
|
||||||
title: 'Header image',
|
|
||||||
type: 'image',
|
|
||||||
description:
|
|
||||||
'This image will be used as the header image in the article page, on the article list/blog page and also eventually on social previews and other websites.',
|
|
||||||
validation: (r) => r.required(),
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
title: 'Alt text',
|
|
||||||
name: 'altText',
|
|
||||||
type: 'string',
|
|
||||||
validation: (r) => r.required(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
options: {
|
|
||||||
hotspot: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'content',
|
|
||||||
title: 'Content',
|
|
||||||
type: 'array',
|
|
||||||
description: 'The body/content of this article.',
|
|
||||||
of: [
|
|
||||||
{
|
|
||||||
type: 'block',
|
|
||||||
marks: {
|
|
||||||
annotations: [
|
|
||||||
{
|
|
||||||
name: 'link',
|
|
||||||
type: 'object',
|
|
||||||
title: 'Link',
|
|
||||||
icon: LinkIcon,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
type: 'url',
|
|
||||||
name: 'href',
|
|
||||||
title: 'URL',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Footnote',
|
|
||||||
name: 'footnote',
|
|
||||||
type: 'object',
|
|
||||||
icon: InsertBelowIcon,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
title: 'Content',
|
|
||||||
name: 'content',
|
|
||||||
type: 'array',
|
|
||||||
of: [{type: 'block'}],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Reference',
|
|
||||||
name: 'referenceToSource',
|
|
||||||
type: 'object',
|
|
||||||
icon: BookIcon,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
title: 'Author(s)',
|
|
||||||
name: 'authors',
|
|
||||||
type: 'string',
|
|
||||||
validation: (r) => r.required(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Date of publication',
|
|
||||||
name: 'dateOfPublication',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Link to reference',
|
|
||||||
name: 'linkToReference',
|
|
||||||
type: 'url',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Title',
|
|
||||||
name: 'title',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: 'Notes',
|
|
||||||
name: 'notes',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'image',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
title: 'Caption',
|
|
||||||
name: 'caption',
|
|
||||||
type: 'array',
|
|
||||||
of: [{type: 'block'}],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Alt text',
|
|
||||||
name: 'altText',
|
|
||||||
type: 'string',
|
|
||||||
validation: (r) => r.required(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
validation: (r) => r.required(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'summary',
|
|
||||||
title: 'Summary',
|
|
||||||
type: 'string',
|
|
||||||
validation: (r) => r.max(160).required(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'readTime',
|
|
||||||
title: 'Read time',
|
|
||||||
type: 'string',
|
|
||||||
description: 'Calculated automatically for you.',
|
|
||||||
readOnly: true,
|
|
||||||
options: {
|
|
||||||
hotspot: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'publishDate',
|
|
||||||
title: 'Publish date',
|
|
||||||
type: 'date',
|
|
||||||
description:
|
|
||||||
'Leave this empty if you want to use the current date (of publish). If you use a date in the past, everything will go normal. If you use a date in the future, the article will be hidden until that day, on which it will be published automatically.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'linkPreviewImage',
|
|
||||||
title: 'Link preview image',
|
|
||||||
type: 'image',
|
|
||||||
description: "If you don't specify this, falls back to 'Header image'",
|
|
||||||
fieldset: 'metadata',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'linkPreviewDescription',
|
|
||||||
title: 'Link preview description',
|
|
||||||
type: 'string',
|
|
||||||
description: "If you don't specify this, falls back to article summary",
|
|
||||||
fieldset: 'metadata',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'twitterCardType',
|
|
||||||
title: 'Twitter card type',
|
|
||||||
type: 'string',
|
|
||||||
description:
|
|
||||||
'See https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started for more info.',
|
|
||||||
options: {
|
|
||||||
list: [
|
|
||||||
{value: 'summary', title: 'Summary'},
|
|
||||||
{
|
|
||||||
value: 'summary_large_image',
|
|
||||||
title: 'Summary with large image',
|
|
||||||
},
|
|
||||||
{value: 'player', title: 'Player'},
|
|
||||||
{value: 'app', title: 'App'},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
initialValue: 'summary_large_image',
|
|
||||||
validation: (r) => r.required(),
|
|
||||||
fieldset: 'metadata',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'language',
|
|
||||||
hidden: true,
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
preview: {
|
|
||||||
select: {
|
|
||||||
title: 'title',
|
|
||||||
media: 'headerImage',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default blogPost
|
|
|
@ -1,4 +0,0 @@
|
||||||
import blogPost from './blogPost'
|
|
||||||
import libraryItem from './libraryItem'
|
|
||||||
|
|
||||||
export const schemaTypes = [blogPost, libraryItem]
|
|
|
@ -1,54 +0,0 @@
|
||||||
import {BookIcon} from '@sanity/icons'
|
|
||||||
import {defineType} from 'sanity'
|
|
||||||
|
|
||||||
const libraryItem = defineType({
|
|
||||||
name: 'libraryItem',
|
|
||||||
title: 'Library items',
|
|
||||||
type: 'document',
|
|
||||||
icon: BookIcon,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'title',
|
|
||||||
title: 'Title',
|
|
||||||
type: 'string',
|
|
||||||
validation: (r) => r.required(),
|
|
||||||
description:
|
|
||||||
'The title of the item. It might be the title of the book, article, podcast episode, etc.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'checkedOut',
|
|
||||||
title: 'Checked out',
|
|
||||||
type: 'boolean',
|
|
||||||
validation: (r) => r.required(),
|
|
||||||
initialValue: false,
|
|
||||||
description: 'If you have already read, listened, etc. to this item.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'link',
|
|
||||||
title: 'Link',
|
|
||||||
type: 'url',
|
|
||||||
validation: (r) => r.required(),
|
|
||||||
description:
|
|
||||||
'You have to provide some link to this content. A link to buy the book, listen to the episode, etc.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'description',
|
|
||||||
title: 'Description',
|
|
||||||
type: 'text',
|
|
||||||
description:
|
|
||||||
'If you wanna give the readers a short description of why this item is good or worth checking out, this is that.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'language',
|
|
||||||
hidden: true,
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
preview: {
|
|
||||||
select: {
|
|
||||||
title: 'title',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default libraryItem
|
|
|
@ -1 +0,0 @@
|
||||||
Files placed here will be served by the Sanity server under the `/static`-prefix
|
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2017",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"incremental": true
|
|
||||||
},
|
|
||||||
"include": ["**/*.ts", "**/*.tsx"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|