diff --git a/README.md b/README.md
index 9040a3e..add45c7 100644
--- a/README.md
+++ b/README.md
@@ -77,7 +77,7 @@ docker run -p 5000:5000 -v /path/to/data:/app/data snippet-box

-- Edditor
+- Editor
- Create and edit your snippets from simple and easy to use editor

diff --git a/client/src/components/Snippets/SnippetForm.tsx b/client/src/components/Snippets/SnippetForm.tsx
index e42da75..d839b25 100644
--- a/client/src/components/Snippets/SnippetForm.tsx
+++ b/client/src/components/Snippets/SnippetForm.tsx
@@ -25,7 +25,8 @@ export const SnippetForm = (props: Props): JSX.Element => {
language: '',
code: '',
docs: '',
- isPinned: false
+ isPinned: false,
+ tags: ''
});
useEffect(() => {
@@ -109,11 +110,31 @@ export const SnippetForm = (props: Props): JSX.Element => {
id='language'
name='language'
value={formData.language}
- placeholder='bash'
+ placeholder='python'
required
onChange={e => inputHandler(e)}
/>
+
+ {/* TAGS */}
+
+
+
inputHandler(e)}
+ />
+
+ Language tag will be added automatically
+
+
{/* CODE SECTION */}
diff --git a/client/src/typescript/interfaces/Snippet.ts b/client/src/typescript/interfaces/Snippet.ts
index 99e517d..8e1ced5 100644
--- a/client/src/typescript/interfaces/Snippet.ts
+++ b/client/src/typescript/interfaces/Snippet.ts
@@ -7,6 +7,7 @@ export interface NewSnippet {
code: string;
docs?: string;
isPinned: boolean;
+ tags: string;
}
export interface Snippet extends Model, NewSnippet {}
diff --git a/package.json b/package.json
index ead2fb6..62db005 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"dev:client": "npm start --prefix=client",
"dev:server": "nodemon",
"dev": "npm-run-all -n --parallel dev:**",
+ "build:client": "npm run build prefix=client",
"build:clear": "rm -rf build",
"build:tsc": "tsc",
"build": "npm-run-all -n build:**"
diff --git a/src/controllers/snippets.ts b/src/controllers/snippets.ts
index 67a524b..fe6a93e 100644
--- a/src/controllers/snippets.ts
+++ b/src/controllers/snippets.ts
@@ -3,7 +3,7 @@ import { QueryTypes } from 'sequelize';
import { sequelize } from '../db';
import { asyncWrapper } from '../middleware';
import { SnippetModel } from '../models';
-import { ErrorResponse } from '../utils';
+import { ErrorResponse, tagsParser } from '../utils';
/**
* @description Create new snippet
@@ -12,7 +12,17 @@ import { ErrorResponse } from '../utils';
*/
export const createSnippet = asyncWrapper(
async (req: Request, res: Response, next: NextFunction): Promise => {
- const snippet = await SnippetModel.create(req.body);
+ const { language, tags } = <{ language: string; tags: string }>req.body;
+ const parsedTags = tagsParser(tags);
+
+ if (!parsedTags.includes(language.toLowerCase())) {
+ parsedTags.push(language.toLowerCase());
+ }
+
+ const snippet = await SnippetModel.create({
+ ...req.body,
+ tags: parsedTags.join(',')
+ });
res.status(201).json({
data: snippet
@@ -81,7 +91,23 @@ export const updateSnippet = asyncWrapper(
);
}
- snippet = await snippet.update(req.body);
+ // Check if language was changed. Edit tags if so
+ const { language: oldLanguage } = snippet;
+ const { language, tags } = <{ language: string; tags: string }>req.body;
+ let parsedTags = tagsParser(tags);
+
+ if (oldLanguage != language) {
+ parsedTags = parsedTags.filter(tag => tag != oldLanguage);
+
+ if (!parsedTags.includes(language)) {
+ parsedTags.push(language.toLowerCase());
+ }
+ }
+
+ snippet = await snippet.update({
+ ...req.body,
+ tags: parsedTags.join(',')
+ });
res.status(200).json({
data: snippet
diff --git a/src/db/migrations/02_tags.ts b/src/db/migrations/02_tags.ts
new file mode 100644
index 0000000..8797d31
--- /dev/null
+++ b/src/db/migrations/02_tags.ts
@@ -0,0 +1,19 @@
+import { DataTypes, QueryInterface, QueryTypes } from 'sequelize';
+import { sequelize } from '../';
+const { STRING } = DataTypes;
+
+export const up = async (queryInterface: QueryInterface): Promise => {
+ await queryInterface.addColumn('snippets', 'tags', {
+ type: STRING,
+ allowNull: true,
+ defaultValue: ''
+ });
+
+ await sequelize.query(`UPDATE snippets SET tags = language`, {
+ type: QueryTypes.UPDATE
+ });
+};
+
+export const down = async (queryInterface: QueryInterface): Promise => {
+ await queryInterface.removeColumn('snippets', 'tags');
+};
diff --git a/src/models/Snippet.ts b/src/models/Snippet.ts
index a435188..c7bb43a 100644
--- a/src/models/Snippet.ts
+++ b/src/models/Snippet.ts
@@ -41,6 +41,11 @@ export const SnippetModel = sequelize.define('Snippet', {
allowNull: true,
defaultValue: 0
},
+ tags: {
+ type: STRING,
+ allowNull: true,
+ defaultValue: ''
+ },
createdAt: {
type: DATE
},
diff --git a/src/typescript/interfaces/Snippet.ts b/src/typescript/interfaces/Snippet.ts
index 785ee09..3d96a2b 100644
--- a/src/typescript/interfaces/Snippet.ts
+++ b/src/typescript/interfaces/Snippet.ts
@@ -8,6 +8,7 @@ export interface Snippet extends Model {
code: string;
docs: string;
isPinned: number;
+ tags: string;
}
export interface SnippetCreationAttributes
diff --git a/src/utils/index.ts b/src/utils/index.ts
index e59e2fd..c86e0f3 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,2 +1,3 @@
export * from './Logger';
export * from './ErrorResponse';
+export * from './tagsParser';
diff --git a/src/utils/tagsParser.ts b/src/utils/tagsParser.ts
new file mode 100644
index 0000000..14fa27e
--- /dev/null
+++ b/src/utils/tagsParser.ts
@@ -0,0 +1,8 @@
+export const tagsParser = (tags: string): string[] => {
+ const parsedTags = tags
+ .split(',')
+ .map(tag => tag.trim().toLowerCase())
+ .filter(String);
+
+ return parsedTags;
+};