diff --git a/.eslintrc.json b/.eslintrc.json index 7ce406a..7adc028 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,6 +5,10 @@ "space-before-blocks": "error", "space-before-function-paren": [2, "never"], "object-curly-spacing": ["error", "always"], - "indent": ["error", 2] + "indent": ["error", 2], + "max-len": ["error", { + "code": 120, + "ignorePattern": "className" + }] } } diff --git a/components/AddLinkDialog.tsx b/components/AddLinkDialog.tsx new file mode 100644 index 0000000..12bd591 --- /dev/null +++ b/components/AddLinkDialog.tsx @@ -0,0 +1,89 @@ +import { Fragment, useState } from 'react' +import { Dialog, Transition } from '@headlessui/react' + +interface AddLinkDialogProps { + open: boolean + close(): void + save(url: string): void +} + +export function AddLinkDialog({ open, close, save }: AddLinkDialogProps) { + const [url, setUrl] = useState() + + return ( + <> + + +
+ + + + + {/* This element is to trick the browser into centering the modal contents. */} + + +
+ + Provider a Link + +
+ setUrl(e.target.value)} + type='text' + placeholder="Link" + className="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out" + /> +
+
+ +
+
+
+
+
+
+ + ) +} \ No newline at end of file diff --git a/components/ArticleItem.tsx b/components/ArticleItem.tsx new file mode 100644 index 0000000..f4762db --- /dev/null +++ b/components/ArticleItem.tsx @@ -0,0 +1,35 @@ +import { memo } from "react" + +interface ArticleItemProps { + imageURL: string, + name: string + tags: string + authors: string + path: string +} + +export const ArticleItem = memo(({ imageURL, tags, authors, name, path }) => { + return ( +
+
+ {name} +
+

{ tags }

+

{ name }

+

{ authors }

+ +
+
+
+ ) +}) + +ArticleItem.displayName = 'ArticleItem' \ No newline at end of file diff --git a/components/Editor.tsx b/components/Editor.tsx new file mode 100644 index 0000000..5e9bbe0 --- /dev/null +++ b/components/Editor.tsx @@ -0,0 +1,395 @@ +import { Fragment, memo, useEffect, useState } from "react" +import { Tiptap } from "./Tiptap" +import TextareaAutosize from 'react-textarea-autosize' +import * as yup from "yup" +import { useForm } from "react-hook-form" +import { yupResolver } from '@hookform/resolvers/yup' +import { OnChangeValue } from "react-select" +import CreatableSelect from "react-select/creatable" +import { useLocalStorageValue } from "@react-hookz/web" +import { + ARTICLE_LICENSE, + ARTICLE_LICENSE_URL, + CREATE_CACHE, + CREATE_USED_AUTHORS, + CREATE_USED_TAGS, + STORAGE_KEY_ACCOUNT_SIG +} from "../constants" +import { addToIPFS } from "../services/IPFSHttpClient" +import { addNFTToNFTStorage } from "../services/NFTStorage" +import axios from "axios" +import router from "next/router" +import { Dialog, Menu, Transition } from '@headlessui/react' +import { ExclamationIcon } from "@heroicons/react/outline" +import { getBrief } from "../web3/utils" +import { Article } from "../types" + +interface EditorProps { + publishLink: string + account: string + cid?: string + article?: Article +} + +interface IFormInputs { + price: string + name: string + description: string + s_tags: string + author: string + files: FileList | string +} +const customStyles = { + menu: (provided, state) => ({ + ...provided, + width: '200px', + borderBottom: '1px dotted pink', + color: state.selectProps.menuColor, + padding: '5px 10px', + }), + control: (provided, state) => ({ + ...provided, + border: 'none', + boxShadow: 'none' + }), + valueContainer: (provided, state) => ({ + ...provided, + }), + dropdownIndicator: (provided, state) => ({ + ...provided, + display: 'none' + }), + indicatorsContainer: (provided, state) => ({ + ...provided, + display: 'none' + }), + multiValue: (provided, state) => { + const opacity = state.isDisabled ? 0.5 : 1 + const transition = 'opacity 300ms' + + return { + ...provided, + opacity, + transition, + background: 'transparent', + borderRadius: '100px', + '& > div': { + color: 'rgb(107 114 128 / var(--tw-text-opacity))', + fontSize: '0.875rem', + lineHeight: '1.25rem', + } + } + } +} + + +const schema = yup.object({ + price: yup.string(), + name: yup.string().required('Title is not optional'), + description: yup.string().required("Content is not optional"), + s_tags: yup.string().required("Tags is not optional"), + author: yup.string().required("Authors Name is not optional"), + files: yup.mixed().test({ test: (value) => value.length, message: "Feature Image is not optional" }), +}).required() + +type Option = { label: string, value: string, __isNew__: boolean } +export const Editor = memo(({ account, article, publishLink, cid }) => { + const [isOpen, setIsOpen] = useState(false) + const [sigInLocal] = useLocalStorageValue(STORAGE_KEY_ACCOUNT_SIG) + const [cachedTags, setCachedTags] = useState([]) + const [cachedAuthors, setCachedAuthors] = useState