make navigation easier

This commit is contained in:
hilmanski
2023-10-05 09:49:30 +08:00
parent 4096fdf313
commit 0bca8f22d7
6 changed files with 309 additions and 263 deletions
+2
View File
@@ -177,6 +177,7 @@ def _scrape_article(link, lang):
article.parse()
content = article.text
content_html = article.article_html
content_title = article.title
# Current logic to detect if it's blocked
# is by check if text results is very short
@@ -246,6 +247,7 @@ def _scrape_article(link, lang):
"link": link,
"totalWords": totalWords,
"keywords": keywords,
"title": content_title,
"headings": headings,
}
+48 -155
View File
@@ -1,19 +1,12 @@
"use client";
import Link from "next/link";
import Terms from "@/components/Terms";
import TextEditor from "@/components/TextEditor";
import { useEffect, useState } from "react";
import Sidebar from "@/components/Sidebar";
async function getData(id: string) {
const res = await fetch(`http://localhost:8000/posts/${id}`)
if (!res.ok) {
throw new Error('Failed to fetch data')
}
return res.json()
}
export default async function Editor({
export default function Editor({
children, params
}: {
children: React.ReactNode;
@@ -21,164 +14,64 @@ export default async function Editor({
id: string;
}
}) {
const data = await getData(params.id);
const searchResult = data.search_result
const [data, setData] = useState({})
const [searchResult, setSearchResult] = useState({})
const [loading, setLoading] = useState(true)
useEffect(() => {
const fetchData = async () => {
const res = await fetch(`http://localhost:8000/posts/${params.id}`)
if (!res.ok) {
throw new Error('Failed to fetch data')
}
const data = await res.json()
setLoading(false)
setData(data)
setSearchResult(data.search_result)
}
fetchData()
.catch(err => {
console.log(err)
})
}, [])
if (loading) {
return (
<div className="my-10 max-w-7xl mx-auto">
<p>Loading...</p>
</div>
)
}
return (
<div className="my-10 max-w-7xl mx-auto">
<div className="flex justify-between mb-5">
<p className="text-xl">
Title: {data.title}
Text Editor
</p>
<Link href="/" className="underline">
Home
</Link>
</div>
<section className="fixed">
<p>Menu</p>
<div className="text-sm">
<a className="block mt-2" href="#answer-box">#Google Results</a>
<a className="block mt-2" href="#related-questions">#Related Questions</a>
<a className="block mt-2" href="#autocomplete">#Autocomplete</a>
<a className="block mt-2" href="#related-searches">#Related Searches</a>
<a className="block mt-2" href="#terms">#Terms</a>
<a className="block mt-2" href="#outline">#Outline</a>
</div>
</section>
<section className="flex justify-between">
<section className="ml-[200px] w-3/12 max-h-screen overflow-y-scroll">
{
searchResult.answer_box && (
<section
id="answer-box"
className="my-5 p-3 border border-emerald-600 text-sm">
<details>
<summary>
<h3 className="inline-block text-emerald-700 ">Answer Box (Featued Snippet)</h3>
</summary>
<p className="italic">
{
searchResult.answer_box.snippet
}
</p>
{
searchResult.answer_box.list && (
<div>
<p className="text-bold italic">Info: Features snippet is a list</p>
{
searchResult.answer_box.list.map((item, index) => {
return (
<div key={index}>
<p>{item}</p>
</div>
)
})
}
</div>
)
}
<p>
<a className="text-sky-700"
href={searchResult.answer_box.link}>
source
</a>
</p>
</details>
</section>
)
}
{
searchResult.related_questions.length > 0 && (
<section
id="related-questions"
className="my-5 p-3 border border-emerald-600 text-sm">
<details>
<summary>
<h3 className="inline-block text-emerald-700 mb-2">Related Questions</h3>
</summary>
{
searchResult.related_questions.map((item, index) => {
return (
<div key={index} className="mb-1">
<p className="font-bold"> {item.question} </p>
<p className="italic"> {item.snippet} </p>
</div>
)
})
}
</details>
</section>
)
}
{
searchResult.autocomplete.length > 0 && (
<section
id="autocomplete"
className="my-5 p-3 border border-emerald-600 text-sm">
<details>
<summary>
<h3 className="inline-block text-emerald-700 mb-2">Autocomplete</h3>
</summary>
{
searchResult.autocomplete.map((item, index) => {
return (
<div key={index} className="mb-1">
{item.value}
</div>
)
})
}
</details>
</section>
)
}
{
searchResult.related_searches.length > 0 && (
<section
id="related-searches"
className="my-5 p-3 border border-emerald-600 text-sm">
<details>
<summary>
<h3 className="inline-block text-emerald-700 mb-2">Related Searches</h3>
</summary>
{
searchResult.related_searches.map((item, index) => {
return (
<div key={index} className="mb-1">
<p className=""> {item.query} </p>
</div>
)
})
}
</details>
</section>
)
}
<Terms id={params.id} />
</section>
<section className="flex justify-betwee">
<TextEditor
id={params.id}
prevContent={data.content} />
prevContent={data.content}
title={data.title} />
<Sidebar
id={params.id}
searchResult={searchResult}
/>
</section>
</div>
)
+79
View File
@@ -0,0 +1,79 @@
export default function Outline({result} : {result: any}) {
return (
<section className="my-5">
{
result.contentInfo && (
<div
className="my-5 text-sm">
<h3 className="mb-5 text-xl font-bold">Competitor reference</h3>
<p> Average words length: {result.averageWords} </p>
<div className="my-5">
{
result.contentInfo.map((site: string, index: number) => {
return (
<div key={index}
className="border-b-2 pb-5">
<p className="border-l-2 border-l-black pl-2 my-2">
<a className="text-sky-700"
href={site.link}>
{site.title}...
</a>
</p>
<p className="mb-2 text-gray-600">
src: {site.link}
</p>
<p>
Words: {site.totalWords}
</p>
{
site.headings.length > 0 && (
<div>
<p>Headings</p>
{
site.headings.map((heading: string, index: number) => {
return (
<p key={index}>
{heading.tag}: {heading.text}
</p>
)
})
}
</div>
)
}
{
site.keywords.length > 0 && (
<div>
<p>keywords</p>
{
site.keywords.map((keyword: string, index: number) => {
return (
<li key={index}
className="inline-block bg-gray-200 mb-1 rounded-full px-3 py-1 text-sm text-gray-700 mr-2">
{
Array.isArray(keyword.word) ? keyword.word.join(' ') : keyword.word
} :
{keyword.frequency}
</li>
)
})
}
</div>
)
}
</div>
)
})
}
</div>
</div>
)
}
</section>
)
}
+177
View File
@@ -0,0 +1,177 @@
"use client"
import { useEffect, useState } from "react";
import Terms from "./Terms";
import Outline from "./Outline";
export default function Sidebar({
id, searchResult
}: {
id: string,
searchResult: any
}) {
const [menu, setMenu] = useState<string>('terms')
const [result, setResult] = useState({})
const [loading, setLoading] = useState(true)
useEffect(() => {
const fetchData = async () => {
const res = await fetch(`http://localhost:8000/scrape/${id}`)
if (!res.ok) {
throw new Error('Failed to fetch data')
}
const _data = await res.json()
setResult(_data.data)
setLoading(false)
}
fetchData()
.catch(err => {
console.log(err)
})
}, [])
return (
<section className="ml-5 w-3/12 bg-gray-100 p-5">
<section className="flex space-x-4 border-b-2 pb-3">
{/* <p>Terms</p> */}
<p onClick={() => {setMenu('terms')}}
className={`
cursor-pointer hover:text-emerald-500
${menu === 'terms' ? 'text-emerald-700 border-b-2 border-b-emerald-700' : ''}
`}>Terms</p>
<p onClick={() => {setMenu('research')}}
className={`
cursor-pointer hover:text-emerald-500
${menu === 'research' ? 'text-emerald-700 border-b-2 border-b-emerald-700' : ''}
`}>
Research</p>
<p onClick={() => {setMenu('outline')}}
className={`
cursor-pointer hover:text-emerald-500
${menu === 'outline' ? 'text-emerald-700 border-b-2 border-b-emerald-700' : ''}
`}>Outline</p>
</section>
{
menu == 'research' && (
<section>
{
searchResult.answer_box && (
<section className="my-5 text-sm">
<h3 className="inline-block text-emerald-700 ">Answer Box (Featued Snippet)</h3>
<p className="italic">
{
searchResult.answer_box.snippet
}
</p>
{
searchResult.answer_box.list && (
<div>
<p className="text-bold italic">Info: Features snippet is a list</p>
{
searchResult.answer_box.list.map((item, index) => {
return (
<div key={index}>
<p>{item}</p>
</div>
)
})
}
</div>
)
}
<p>
<a className="text-sky-700"
href={searchResult.answer_box.link}>
source
</a>
</p>
</section>
)
}
{
searchResult.related_questions.length > 0 && (
<section className="my-5 text-sm">
<h3 className="inline-block text-emerald-700 mb-2">Related Questions</h3>
{
searchResult.related_questions.map((item, index) => {
return (
<div key={index} className="mb-1">
<details>
<summary>
<p className="inline"> {item.question} </p>
</summary>
<p className="italic"> {item.snippet} </p>
</details>
</div>
)
})
}
</section>
)
}
{
searchResult.autocomplete.length > 0 && (
<section className="my-5 text-sm">
<h3 className="inline-block text-emerald-700 mb-2">Autocomplete</h3>
{
searchResult.autocomplete.map((item, index) => {
return (
<div key={index} className="mb-1">
{item.value}
</div>
)
})
}
</section>
)
}
{
searchResult.related_searches.length > 0 && (
<section className="my-5 text-sm">
<h3 className="inline-block text-emerald-700 mb-2">Related Searches</h3>
{
searchResult.related_searches.map((item, index) => {
return (
<div key={index} className="mb-1">
<p className=""> {item.query} </p>
</div>
)
})
}
</section>
)
}
</section>
)}
{
menu == 'terms' && (
<Terms result={result} />
)
}
{
menu == 'outline' && (
<Outline result={result} />
)
}
</section>
)
}
+1 -106
View File
@@ -1,41 +1,7 @@
"use client";
import { useEffect, useState } from "react";
export default function Terms({id} : {id: string}) {
const [result, setResult] = useState({} as any)
const [loading, setLoading] = useState(true)
useEffect(() => {
getData(id).then((data) => {
setResult(data.data)
})
}, [])
async function getData(id: string) {
const res = await fetch(`http://localhost:8000/scrape/${id}`)
if (!res.ok) {
throw new Error('Failed to fetch data')
}
setLoading(false)
return res.json()
}
if (loading) {
return (
<section className="my-5">
<h2>Terms (Take up to 1 minute)</h2>
Loading...
</section>
)
}
export default function Terms({result} : {result: any}) {
return (
<section className="my-5">
{
result.topKeywords && (
<div
@@ -57,77 +23,6 @@ export default function Terms({id} : {id: string}) {
</div>
)
}
{
result.contentInfo && (
<div
id="outline"
className="my-5 border p-5 border-green-700 text-sm">
<h3 className="mb-5 text-xl font-bold">Competitor reference</h3>
<p> Average words length: {result.averageWords} </p>
<div className="flex justify-between space-x-5 overflow-x-scroll">
{
result.contentInfo.map((site: string, index: number) => {
return (
<div key={index}
className="border border-green-200 p-2">
<p>
Site: &nbsp;
<a className="text-sky-700"
href={site.link}>
{site.link.substring(0, 30)}...
</a>
</p>
<p>
Words: {site.totalWords}
</p>
{
site.headings.length > 0 && (
<div>
<p>Headings</p>
{
site.headings.map((heading: string, index: number) => {
return (
<p key={index}>
{heading.tag}: {heading.text}
</p>
)
})
}
</div>
)
}
{
site.keywords.length > 0 && (
<div>
<p>keywords</p>
{
site.keywords.map((keyword: string, index: number) => {
return (
<li key={index}
className="inline-block bg-gray-200 mb-1 rounded-full px-3 py-1 text-sm text-gray-700 mr-2">
{
Array.isArray(keyword.word) ? keyword.word.join(' ') : keyword.word
} :
{keyword.frequency}
</li>
)
})
}
</div>
)
}
</div>
)
})
}
</div>
</div>
)
}
</section>
)
}
+2 -2
View File
@@ -2,7 +2,7 @@
import { useState } from "react";
export default function TextEditor({id, prevContent}: {id: string, prevContent: string}) {
export default function TextEditor({id, prevContent, title}: {id: string, prevContent: string, title: string}) {
const [content, setContent] = useState<string>(prevContent)
@@ -24,7 +24,7 @@ export default function TextEditor({id, prevContent}: {id: string, prevContent:
return (
<section className="ml-5 w-8/12">
<div className="flex justify-between items-center mb-5">
<p className="font-bold text-xl">Text Editor</p>
<p className="font-bold text-xl">Title: {title}</p>
<button
onClick={() => {saveContent()}}
className="bg-sky-400 hover:bg-sky-600 p-3 rounded-lg text-white">