Skip to content
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
14 changes: 14 additions & 0 deletions src/components/LoginMiddleware.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useEffect, ReactNode } from 'react';
import { isLoggedIn } from '../utils/isloggedIn';

Check failure on line 2 in src/components/LoginMiddleware.tsx

View workflow job for this annotation

GitHub Actions / Build

Cannot find module '../utils/isloggedIn' or its corresponding type declarations.

const LoginMiddleware: React.FC<{ children: ReactNode }> = ({ children }) => {
useEffect(() => {
if (!isLoggedIn() && window.location.pathname !== '/login') {
window.location.href = '/login';
}
}, []);

return <>{children}</>;
};

export default LoginMiddleware;
44 changes: 30 additions & 14 deletions src/components/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { authors, types, categories } from '../../types/options';
import SelectComponent from './SelectComponent';
import CustomToolbar from './toolbar/CustomToolbar';
import { saveArticle } from '../../api/article';
import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';

// Tiptap 기본 확장
import StarterKit from '@tiptap/starter-kit';
Expand All @@ -22,17 +23,15 @@ import Table from '@tiptap/extension-table';
import TableHeader from '@tiptap/extension-table-header';
import TableCell from '@tiptap/extension-table-cell';
import TableRow from '@tiptap/extension-table-row';
// import Image from '@tiptap/extension-image';
import Link from '@tiptap/extension-link';
import { getLinkOptions } from './common/Link';

// List Extension
import ListItem from '@tiptap/extension-list-item';
import Blockquote from '@tiptap/extension-blockquote';
import BulletList from '@tiptap/extension-bullet-list';
import OrderedList from '@tiptap/extension-ordered-list';

// Custom Extension
import { getLinkOptions } from './common/Link';
import CustomPaywall from './customComponent/CustomPaywall';
import CustomPhoto from './customComponent/CustomPhoto';
import CustomFile from './customComponent/CustomFile';
Expand Down Expand Up @@ -87,14 +86,10 @@ const Editor = ({ content }: { content: JSONContent[] | null }) => {
className: 'rounded-3 border border-blue-500',
mode: 'all',
}),

// 텍스트
Color.configure({ types: [TextStyle.name, ListItem.name] }),
Placeholder.configure({
placeholder: '내용을 입력하세요.',
}),
TextStyle,
Underline,
Highlight.configure({ multicolor: true }),
TextAlign.configure({
types: ['paragraph', 'image', 'blockquote', 'horizontal_rule', 'file'],
Expand All @@ -104,20 +99,41 @@ const Editor = ({ content }: { content: JSONContent[] | null }) => {
class: 'border-l-3 border-gray-300 pl-4 m-6',
},
}),

// 커스텀 콘텐츠
Link.configure(getLinkOptions()),
// Image,
CustomPhoto,
CustomFile,
CustomPaywall,
Table.configure({
resizable: true,
}),
TextStyle,
Underline,
TableHeader,
TableRow,
TableCell,

// 커스텀 콘텐츠
CustomPhoto,
CustomFile,
CustomPaywall,
],
editorProps: {
handlePaste(view, event) {
const html = event.clipboardData?.getData('text/html');
console.log(html);
if (html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const body = doc.body;

const fragment = ProseMirrorDOMParser.fromSchema(
view.state.schema,
).parse(body);

const transaction = view.state.tr.replaceSelectionWith(fragment);
view.dispatch(transaction);
return true;
}
return false;
},
},
});

useEffect(() => {
Expand Down Expand Up @@ -189,7 +205,7 @@ const Editor = ({ content }: { content: JSONContent[] | null }) => {
<h1 className="p-4 pl-20 border-b-2 border-b-gray-200">
<input
type="text"
className="w-full text-2xl font-bold"
className="w-full text-[40px] font-bold font-['Pretendard'] leading-[56px]"
placeholder="제목"
value={articleData.title}
onChange={(e) =>
Expand Down
39 changes: 39 additions & 0 deletions src/components/editor/customComponent/CustomLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Node, mergeAttributes } from '@tiptap/core';

const CustomLinkNode = Node.create({
name: 'customLink',

group: 'inline',

inline: true,

selectable: false,

addAttributes() {
return {
href: {
default: null,
},
title: {
default: null,
},
description: {
default: null,
},
};
},

parseHTML() {
return [
{
tag: 'a[data-custom-link]',
},
];
},

renderHTML({ HTMLAttributes }) {
return ['a', mergeAttributes({ 'data-custom-link': true }, HTMLAttributes), HTMLAttributes.title];
},
});

export default CustomLinkNode;
94 changes: 74 additions & 20 deletions src/components/editor/customComponent/CustomPaywall.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,109 @@
import { Node, mergeAttributes } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react';
import PaywallComponent from './PaywallComponent'; // React 컴포넌트로 렌더링
import { Node } from '@tiptap/core';

const CustomPaywall = Node.create({
name: 'paywall',

group: 'block', // 블록 요소로 처리
atom: true, // 독립적 요소
group: 'block',
atom: true,

addAttributes() {
return {
alignment: { default: 'mr-auto ml-0' },
title: { default: '프리미엄 구독자 전용 콘텐츠입니다.' },
description: {
default: '모아가이드 구독으로 더 많은 콘텐츠를 만나보세요!',
},
buttonText: { default: '프리미엄 구독하기' },
info: {
default: '콘텐츠 이용권한이 없는 경우 여기까지만 확인 가능합니다.',
},
brInfo: {
default: '콘텐츠 판매 설정에 따라 문구 및 버튼이 변경될 수 있습니다.',
default: `콘텐츠 이용권한이 없는 경우 여기까지만 확인 가능합니다.\n콘텐츠 판매 설정에 따라 문구 및 버튼이 변경될 수 있습니다.`,
},
};
},

parseHTML() {
return [
{
tag: 'div.se_paywall',
tag: 'div.se-section.se-section-custom.se-l-default.se-section-align-left',
getAttrs: (element) => {
const alignment = element.classList.contains(
'se-section-align-center',
)
? 'mx-auto'
: element.classList.contains('se-section-align-right')
? 'ml-auto mr-0'
: 'mr-auto ml-0';

const paywallElement = element.querySelector('.se_paywall');
if (!paywallElement) {
return false;
}

const title =
paywallElement.querySelector('.se_paywall_title')?.textContent ||
'';
const description =
paywallElement.querySelector('.se_paywall_desc')?.textContent || '';
const buttonText =
paywallElement.querySelector('.se_paywall_subscribe')
?.textContent || '';
const info = Array.from(
paywallElement.querySelector('.se_paywall_info')?.childNodes || [],
)
.map((node) => (node.nodeName === 'BR' ? '\n' : node.textContent))
.join('');

return { alignment, title, description, buttonText, info };
},
},
];
},

renderHTML({ HTMLAttributes }) {
const { alignment, title, description, buttonText, info } = HTMLAttributes;

return [
'div',
mergeAttributes(HTMLAttributes, { class: 'se_paywall' }),
{
class: `block mx-[-20px] relative ${alignment}`,
},
[
'div',
{ class: 'se_paywall_text' },
['strong', { class: 'se_paywall_title' }, HTMLAttributes.title],
['p', { class: 'se_paywall_desc' }, HTMLAttributes.description],
['a', { class: 'se_paywall_subscribe' }, HTMLAttributes.buttonText],
{
class: `p-[28px_20px_0] tracking-[-0.5px] text-center`,
},
[
'div',
{ class: 'px-[11px] py-[20px] text-[#303038]' },
['strong', { class: 'text-[18px] leading-[24px]' }, title],
[
'p',
{ class: 'mt-[3px] text-[14px] leading-[20px] opacity-75' },
description,
],
[
'a',
{
class:
'overflow-hidden block mt-[31px] px-[15px] py-[13px] text-[17px] font-semibold leading-[20px] text-[#222] tracking-[-0.5px] rounded-[3px] shadow-md border border-[rgba(255,255,255,0.09)] bg-gradient-to-r from-[#e6b459] to-[#e9a750]',
},
buttonText,
],
],
[
'p',
{
class:
'pt-[20px] pb-[18px] text-[14px] leading-[20px] text-[#999] border-t border-[rgba(0,0,0,0.1)]',
},
...info
.split('\n')
.flatMap((line: string, index: number, array: string[]) =>
index < array.length - 1 ? [line, ['br']] : [line],
),
],
],
['p', { class: 'se_paywall_info' }, HTMLAttributes.info],
];
},

addNodeView() {
return ReactNodeViewRenderer(PaywallComponent); // React 컴포넌트와 연결
},
});

export default CustomPaywall;
2 changes: 1 addition & 1 deletion src/components/editor/icons/CustomButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const CustomIcon = {
AddLink: ({ editor }: { editor: Editor }) => (
<button
onClick={() => {
const url = window.prompt('Enter the URL');
const url = prompt('링크를 입력하세요:');
if (url) {
editor
.chain()
Expand Down
5 changes: 4 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.tsx';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import LoginMiddleware from './components/LoginMiddleware.tsx';

const queryClient = new QueryClient();

createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<LoginMiddleware>
<App />
</LoginMiddleware>
</QueryClientProvider>
</StrictMode>,
);
8 changes: 8 additions & 0 deletions src/utils/isLoggedIn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { getCookie } from './useCookie';
export const isLoggedIn = () => {
const token = getCookie('token');
if (token) {
return true;
}
return false;
};
22 changes: 22 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,11 @@
dependencies:
tippy.js "^6.3.7"

"@tiptap/extension-focus@^2.11.2":
version "2.11.2"
resolved "https://registry.yarnpkg.com/@tiptap/extension-focus/-/extension-focus-2.11.2.tgz#bad95040e9466051add7a515c83702babf40140a"
integrity sha512-BLmd7tED5FJUwyMmz8jLsok16SxBwAvavS2TwExVVCX9ABSBc3nHA9vv5sGNbTOOS9QDWYTzVH5Zq0cbgj86Gg==

"@tiptap/extension-gapcursor@^2.11.2":
version "2.11.2"
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.2.tgz#9a44175bca5eb13e5281f22c6c64d6b9e3db326e"
Expand Down Expand Up @@ -848,6 +853,13 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.11.2.tgz#f74051e6c767680bf43039b8f152736c0553f13e"
integrity sha512-652oTa+iDiR7sMtmePSy+303HSNJxvxmV/6IvQoMdffJU0oPiWcWnCCL0qrWgtHh15dplj36EtB/znENWbvVOw==

"@tiptap/extension-link@^2.11.2":
version "2.11.2"
resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.11.2.tgz#46096d6862a2c4f8dd18e9db9204959e4b1dbe0d"
integrity sha512-Mbre+JotLMUg9jdWWrwIReiRVMkA2kMzmtD2Aqy/n5P+wuI84898qIZSkhPEzDOGzp0mluUO/iGsz0NdTto/JQ==
dependencies:
linkifyjs "^4.2.0"

"@tiptap/extension-list-item@^2.11.2":
version "2.11.2"
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.11.2.tgz#4b81f934bdbe98ee5e1dabdb799f2f3bf095c44e"
Expand All @@ -863,6 +875,11 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.11.2.tgz#b4e99129b1c5959e2926b9563ced89755e0d4d81"
integrity sha512-iydTjeZbPJuqctOaAx7QebLPvz9J/hBxPptuhe4GZmqInknAk7+SFJagYeGNb14wfXKOvDZ9DMqv6mBiqSA90Q==

"@tiptap/extension-placeholder@^2.11.2":
version "2.11.2"
resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.11.2.tgz#de68ef4932141d6344b0c75f18916249c387883b"
integrity sha512-7rv6nylqX57Q+K+AH794Kg9U7OrLyujhXXqQvd9iZdBP7bTCNUlFu0cGlIyHdM/eWJjoUblZs0VLV2IApk4xjQ==

"@tiptap/extension-strike@^2.11.2":
version "2.11.2"
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.11.2.tgz#9b5b698da5a024219e2ecf438c3b503d9b812fbe"
Expand Down Expand Up @@ -1937,6 +1954,11 @@ linkify-it@^5.0.0:
dependencies:
uc.micro "^2.0.0"

linkifyjs@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08"
integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==

locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
Expand Down
Loading