Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Créer un article depuis un workspace #1183

Merged
merged 3 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 19 additions & 22 deletions docs/src/en/tutorials/api-graphql.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: "GraphQL API"
title: 'GraphQL API'
---

## Stylo's GraphQL API - An introduction
Expand All @@ -14,9 +14,9 @@ GraphQL stands for **Graph Query Language**. It's a query language and runtime e

### Why use GraphQL?

- This environment is very easy to use.
- It lets you visualize and manipulate your data in Stylo.
- You get exactly what you ask for, and no more. You can have several types of data in a single request.
- This environment is very easy to use.
- It lets you visualize and manipulate your data in Stylo.
- You get exactly what you ask for, and no more. You can have several types of data in a single request.
- And it's fast.
- You don't need to know how to code to use this environment, as the available requests are listed in the documentation.

Expand All @@ -32,11 +32,11 @@ Once you've installed the extension or environment, make sure you enter the URL

3. API key

You also need to enter your API key. In the Stylo application, click on your name, the drop-down menu will open and then you click on your e-mail address. This will take you to your account information. This is where you'll find your key. Copy it. Back in GraphQL, on the bottom left is the "HTTP HEADERS" tab. Enter the key as follows:
You also need to enter your API key. In the Stylo application, click on your name, the drop-down menu will open and then you click on your e-mail address. This will take you to your account information. This is where you'll find your key. Copy it. Back in GraphQL, on the bottom left is the "HTTP HEADERS" tab. Enter the key as follows:

```graphql
{
"Authorization": "YOUR API KEY"
"Authorization": "YOUR API KEY"
}
```

Expand All @@ -52,19 +52,19 @@ In this introduction to Stylo's GraphQL API, we'll look at how to use queries an

### Queries

Simply request a query. This is the first word in your request.
In the first example, we're asking for a list of all your articles contained in Stylo. In square brackets, we specify what other information we'd like to have. In this case, we'd like to know the associated user, the title of the article and its identifier.
Simply request a query. This is the first word in your request.
In the first example, we're asking for a list of all your articles contained in Stylo. In square brackets, we specify what other information we'd like to have. In this case, we'd like to know the associated user, the title of the article and its identifier.

You can, of course, request other information as well. The possibilities are immense, and they go hand in hand with your needs.
Don't forget to close the brackets after opening each one.When you're ready, click the execute button.
You can, of course, request other information as well. The possibilities are immense, and they go hand in hand with your needs.
Don't forget to close the brackets after opening each one.When you're ready, click the execute button.

```graphql
Example 1:
query allMyArticles {
user {
_id
email

articles {
_id
title
Expand All @@ -89,7 +89,7 @@ query articles {
}
```

For this last example, once again you need to enter your article ID in the appropriate space. This time, GraphQL shows you not only the title of your article and the contributors, but also the Markdown, Yaml and BibTex it contains!
For this last example, once again you need to enter your article ID in the appropriate space. This time, GraphQL shows you not only the title of your article and the contributors, but also the Markdown, Yaml and BibTex it contains!

```graphql
Example 3 :
Expand All @@ -103,34 +103,31 @@ query {

```

You may have noticed that the application offers you autocompletion options as you write. This gives you examples of what you can ask for later.
You may have noticed that the application offers you autocompletion options as you write. This gives you examples of what you can ask for later.

You'll also find a complete list in the tab to the left of the screen called "Schema" . If you click on it, the tab opens.
You'll also find a complete list in the tab to the left of the screen called "Schema" . If you click on it, the tab opens.
The API documentation tab is one of GraphQL Playground's most interesting features. It lets you preview all possible queries and mutations, along with their details in a single field of a given schema.

![Schema](https://upload.wikimedia.org/wikipedia/commons/c/c6/Capture_d%E2%80%99%C3%A9cran_2024-01-23_184801.png)


### Mutations

In addition to queries, you can also use mutations in the interface.
What are mutations? Mutations are another form of query. However, all operations that cause writes must be sent explicitly via a mutation. Put simply, while queries allow you to view your data, mutations are used to create, modify or delete data or content.
In addition to queries, you can also use mutations in the interface.
What are mutations? Mutations are another form of query. However, all operations that cause writes must be sent explicitly via a mutation. Put simply, while queries allow you to view your data, mutations are used to create, modify or delete data or content.

Let's take a look at the list in the "Schema" tab: ![Mutations](https://upload.wikimedia.org/wikipedia/commons/4/48/Capture_d%E2%80%99%C3%A9cran_2024-01-23_191722.png)

You can create articles, share your articles, duplicate them and much more. The list goes on and on.

Let's look at an example of a mutation:

Let's look at an example of a mutation:

```graphql
mutation{createArticle(title: "ARTICLE TITLE",
user:" YOUR ID ")
mutation{createArticle(createArticleInput: { "title": "ARTICLE TITLE" })
{title _id}}

```

In this example, we're asking the API to create an article for us. To do this, enter your ID number, which you'll find either in your Stylo account information or which you can request in GraphQL Playground. Then enter the title you want in the appropriate space. Once the mutation has been launched, return to Stylo's "Articles" page and you'll see your new article with the chosen title.
In this example, we're asking the API to create an article for us. To do this, enter your ID number, which you'll find either in your Stylo account information or which you can request in GraphQL Playground. Then enter the title you want in the appropriate space. Once the mutation has been launched, return to Stylo's "Articles" page and you'll see your new article with the chosen title.

## Finally

Expand Down
150 changes: 78 additions & 72 deletions front/src/components/ArticleCreate.jsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
import { Button, useInput, useToasts } from '@geist-ui/core'
import { useToasts } from '@geist-ui/core'
import React, { useState, useCallback, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'

import etv from '../helpers/eventTargetValue'
import { useGraphQL } from '../helpers/graphQL'
import { createArticle } from './Articles.graphql'
import Field from './Field.jsx'
import { getTags } from './Tag.graphql'
import Button from './Button.jsx'
import Checkbox from './Checkbox.jsx'

import styles from './articleCreate.module.scss'
import ArticleTag from './Tag'
import { useCurrentUser } from '../contexts/CurrentUser'
import formStyles from './field.module.scss'
import checkboxStyles from './Checkbox.module.scss'
import { fromFormData } from '../helpers/forms.js'

export default function ArticleCreate({ onSubmit }) {
/**
* @typedef {Object} ArticleCreateProps
* @property {function=} onSubmit
* @property {string=} workspaceId
*/

/**
* @param {ArticleCreateProps} props
* @returns {React.ReactHTMLElement}
*/
export default function ArticleCreate({ onSubmit, workspaceId = null }) {
const { t } = useTranslation()
const { setToast } = useToasts()
const { state: title, bindings: titleBindings } = useInput('')
const titleInputRef = useRef()

const [tags, setTags] = useState([])
const [selectedTagIds, setSelectedtagIds] = useState([])
const runQuery = useGraphQL()
const activeUser = useCurrentUser()

useEffect(() => {
if (titleInputRef.current !== undefined) {
titleInputRef.current.focus()
}
}, [titleInputRef])
const workspaces = useSelector((state) => state.activeUser.workspaces)

useEffect(() => {
// Self invoking async function
Expand All @@ -45,80 +50,81 @@ export default function ArticleCreate({ onSubmit }) {
})()
}, [])

const handleSubmit = useCallback(
async (event) => {
try {
event.preventDefault()
const result = await runQuery({
query: createArticle,
variables: { user: activeUser._id, title, tags: selectedTagIds },
})
const createdArticle = {
...result.createArticle,
tags: result.createArticle.addTags,
}
delete createdArticle.addTags
onSubmit(createdArticle)
setToast({
text: t('article.create.successNotification'),
type: 'default',
})
} catch (err) {
setToast({
text: t('article.create.errorNotification', { errMessage: err }),
type: 'error',
})
}
},
[title, selectedTagIds]
)

const toggleCheckedTags = useCallback(
(event) => {
const _id = etv(event)
selectedTagIds.includes(_id)
? setSelectedtagIds(selectedTagIds.filter((tagId) => tagId !== _id))
: setSelectedtagIds([...selectedTagIds, _id])
},
[selectedTagIds]
)
const handleSubmit = useCallback(async (event) => {
try {
event.preventDefault()
const createArticleInput = fromFormData(event.target)
const { createArticle: createdArticle } = await runQuery({
query: createArticle,
variables: { createArticleInput },
})
onSubmit(createdArticle)
setToast({
text: t('article.create.successNotification'),
type: 'default',
})
} catch (err) {
setToast({
text: t('article.create.errorNotification', { errMessage: err }),
type: 'error',
})
}
}, [])

return (
<section>
<form onSubmit={handleSubmit} className={styles.form}>
<form onSubmit={handleSubmit} className={formStyles.form}>
<Field
ref={titleInputRef}
{...titleBindings}
autoFocus={true}
label={t('article.createForm.titleField')}
type="text"
className={styles.titleField}
name="title"
required={true}
/>

{tags.length > 0 && (
<div className={styles.field}>
<label>{t('article.createForm.tagsField')}</label>
<ul className={styles.tags}>
<div>
<span className={formStyles.fieldLabel}>
{t('article.createForm.tagsField')}
</span>

<ul className={checkboxStyles.inlineList}>
{tags.map((t) => (
<li key={`selectTag-${t._id}`}>
<ArticleTag
tag={t}
checked={selectedTagIds.includes(t._id)}
name={`selectTag-${t._id}`}
onClick={toggleCheckedTags}
disableAction={false}
/>
<Checkbox name="tags[]" value={t._id} color={t.color}>
{t.name}
</Checkbox>
</li>
))}
</ul>
</div>
)}

{workspaces.length > 0 && (
<div>
<span className={formStyles.fieldLabel}>
{t('workspace.title')}
</span>

<ul className={checkboxStyles.inlineList}>
{workspaces.map((workspace) => (
<li key={`selectWorkspace-${workspace._id}`}>
<Checkbox
name="workspaces[]"
value={workspace._id}
color={workspace.color}
defaultChecked={workspaceId === workspace._id}
>
{workspace.name}
</Checkbox>
</li>
))}
</ul>
</div>
)}
<ul className={styles.actions}>
<li>
<Button
type="secondary"
className={styles.button}
title={t('article.createForm.buttonTitle')}
onClick={handleSubmit}
>
<Button primary className={styles.button}>
{t('article.createForm.buttonText')}
</Button>
</li>
Expand Down
10 changes: 2 additions & 8 deletions front/src/components/Articles.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ query getWorkspaceArticles($workspaceId: ID!) {
}
}

mutation createArticle($title: String!, $user: ID!, $tags: [ID]!) {
createArticle(title: $title, user: $user) {
mutation createArticle($createArticleInput: CreateArticleInput!) {
createArticle(createArticleInput: $createArticleInput) {
_id
title
updatedAt
Expand All @@ -161,11 +161,5 @@ mutation createArticle($title: String!, $user: ID!, $tags: [ID]!) {
color
_id
}

addTags(tags: $tags) {
_id
name
color
}
}
}
23 changes: 9 additions & 14 deletions front/src/components/Articles.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
Loading,
Modal as GeistModal,
useModal,
} from '@geist-ui/core'
import { Loading, Modal as GeistModal, useModal } from '@geist-ui/core'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { shallowEqual, useSelector } from 'react-redux'
Expand Down Expand Up @@ -253,14 +249,10 @@ export default function Articles() {
</aside>

<div className={styles.articlesTableHeader}>
{!activeWorkspaceId && (
<Button
primary
onClick={() => setCreateArticleVisible(true)}
>
{t('article.createAction.buttonText')}
</Button>
)}
<Button primary onClick={() => setCreateArticleVisible(true)}>
{t('article.createAction.buttonText')}
</Button>

<div className={styles.articleCounter}>
{t('article.count', { count: keepArticles.length })}
</div>
Expand All @@ -273,7 +265,10 @@ export default function Articles() {
>
<h2>{t('article.createModal.title')}</h2>
<GeistModal.Content>
<ArticleCreate onSubmit={handleArticleCreated} />
<ArticleCreate
onSubmit={handleArticleCreated}
workspaceId={activeWorkspaceId}
/>
</GeistModal.Content>
<GeistModal.Action
passive
Expand Down
Loading
Loading