Skip to content
Merged
26 changes: 26 additions & 0 deletions migrations/versions/2e86822886aa_add_ext_to_webpages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Add ext to webpages

Revision ID: 2e86822886aa
Revises: ac63c9eebbec
Create Date: 2025-06-03 11:38:42.644973

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '2e86822886aa'
down_revision = 'ac63c9eebbec'
branch_labels = None
depends_on = None


def upgrade():
op.add_column(
"webpages", sa.Column("ext", sa.String(), nullable=True)
)


def downgrade():
op.drop_column("webpages", "ext")
22 changes: 19 additions & 3 deletions static/client/components/Breadcrumbs/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import { useLocation, useNavigate } from "react-router-dom";

import { type IBreadcrumb } from "./Breadcrumbs.types";

import { findPage } from "@/services/tree/pages";
import { useStore } from "@/store";

const Breadcrumbs = () => {
const location = useLocation();
const [breadcrumbs, setBreadcrumbs] = useState<IBreadcrumb[]>([]);
const navigate = useNavigate();
const selectedProject = useStore((state) => state.selectedProject);

useEffect(() => {
const pageIndex = location.pathname.indexOf("app/webpage/");
Expand Down Expand Up @@ -37,14 +41,26 @@ const Breadcrumbs = () => {
[navigate],
);

function isValidPage(path: string) {
if (!selectedProject) return false;
const pageUrl = path.split(`/${selectedProject.name}`)[1];
if (!pageUrl) return true; // Means it's parent index
const page = findPage(selectedProject.templates, pageUrl, "", true);
return page && typeof page === "object" && "ext" in page && page.ext !== ".dir";
}

return (
<div className="l-breadcrumbs">
{breadcrumbs.map((bc, index) => (
<React.Fragment key={`bc-${index}`}>
{index < breadcrumbs.length - 1 ? (
<a className="p-text--small-caps" href={bc.link} onClick={(e) => goToPage(e, bc.link)}>
{bc.name}
</a>
isValidPage(bc.link) ? (
<a className="p-text--small-caps" href={bc.link} onClick={(e) => goToPage(e, bc.link)}>
{bc.name}
</a>
) : (
<span className="p-text--small-caps">{bc.name}</span>
)
) : (
<span className="p-text--small-caps">{bc.name}</span>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ const NavigationElement = ({ activePageName, page, project, onSelect }: INavigat
setExpanded((prevValue) => !prevValue);
setChildrenHidden((prevValue) => !prevValue);
} else {
onSelect(page.name);
if (page.ext !== ".dir") onSelect(page.name);
else {
setExpanded((prevValue) => !prevValue);
setChildrenHidden((prevValue) => !prevValue);
}
}
},
[expandButtonRef, page, onSelect],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface TableViewRowItemProps {
const TableViewRowItem: React.FC<TableViewRowItemProps> = ({ page }) => {
return (
<>
<TableViewRow page={page} />
{page.ext !== ".dir" && <TableViewRow page={page} />}
{page?.children?.map((child) => {
return <TableViewRowItem key={`${child.project?.name}${child.url}`} page={child} />;
})}
Expand Down
1 change: 1 addition & 0 deletions static/client/services/api/types/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface IPage {
name: string;
updated_at: string;
};
ext?: string;
}

export interface IPagesResponse {
Expand Down
13 changes: 10 additions & 3 deletions static/client/services/tree/pages.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import type { IPage } from "@/services/api/types/pages";

// recursively find the page in the tree by the given name (URL)
export function findPage(tree: IPage, pageName: string, prefix: string = ""): boolean {
export function findPage(
tree: IPage,
pageName: string,
prefix: string = "",
returnObject: boolean = false,
): boolean | IPage {
const parts = pageName.split("/");

for (let i = 0; i < tree.children.length; i += 1) {
if (tree.children[i].name === `${prefix}/${parts[1]}`) {
if (parts.length > 2) {
return findPage(tree.children[i], `/${parts.slice(2).join("/")}`, `${prefix}/${parts[1]}`);
return findPage(tree.children[i], `/${parts.slice(2).join("/")}`, `${prefix}/${parts[1]}`, returnObject);
}
return true;
return returnObject ? tree.children[i] : true;
}
}

return false;
}

Expand Down
1 change: 1 addition & 0 deletions webapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class Webpage(db.Model, DateTimeMixin):
parent_id: int = Column(Integer, ForeignKey("webpages.id"))
owner_id: int = Column(Integer, ForeignKey("users.id"))
status: str = Column(Enum(WebpageStatus), default=WebpageStatus.AVAILABLE)
ext: str = Column(String, nullable=True)

project = relationship("Project", back_populates="webpages")
owner = relationship("User", back_populates="webpages")
Expand Down
33 changes: 30 additions & 3 deletions webapp/parse_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"link": ["meta_copydoc"],
}

EXCLUDE_PATHS = ["partials"]


def is_index(path):
return path.name == "index.html"
Expand All @@ -39,6 +41,17 @@ def is_template(path):
return False


def is_partial(path):
"""
Return True if the file name starts with an underscore,
that indicates it as a partial

Partials are templates that are not meant to be rendered directly, but
included in other templates.
"""
return path.name.startswith("_")


def append_base_path(base, path_name):
"""
Add the base (root) to a path URI.
Expand Down Expand Up @@ -204,7 +217,10 @@ def is_valid_page(path, extended_path, is_index=True):
- They contain the same extended path as the index html.
- They extend from the base html.
"""
if is_template(path):

path = Path(path)

if not path.is_file() or is_template(path) or is_partial(path):
return False

if not is_index and extended_path:
Expand All @@ -223,8 +239,9 @@ def get_extended_path(path):
with path.open("r") as f:
for line in f.readlines():
# TODO: also match single quotes \'
if match := re.search("{% extends [\"'](.*?)[\"'] %}", line):
return match.group(1)
if ".html" in str(path):
if match := re.search("{% extends [\"'](.*?)[\"'] %}", line):
return match.group(1)


def update_tags(tags, new_tags):
Expand All @@ -245,6 +262,7 @@ def create_node():
"description": None,
"link": None,
"children": [],
"ext": None,
}


Expand All @@ -256,6 +274,11 @@ def scan_directory(path_name, base=None):
node = create_node()
node["name"] = path_name.split("/templates", 1)[-1]

# Skip scanning directory if it is in excluded paths
for path in EXCLUDE_PATHS:
if re.search(path, node["name"]):
return node

# We get the relative parent for the path
if base is None:
base = node_path.absolute()
Expand All @@ -278,6 +301,9 @@ def scan_directory(path_name, base=None):
tags = get_tags_rolling_buffer(index_path)
node = update_tags(node, tags)

else:
node["ext"] = ".dir"

# Cycle through other files in this directory
for child in node_path.iterdir():
# If the child is a file, check if it is a valid page
Expand All @@ -287,6 +313,7 @@ def scan_directory(path_name, base=None):
child, extended_path, is_index=False
):
child_tags = get_tags_rolling_buffer(child)
child_tags["ext"] = child.suffix
# If the child has no copydocs link, use the parent's link
if not child_tags.get("link") and extended_path:
child_tags["link"] = get_extended_copydoc(
Expand Down
6 changes: 5 additions & 1 deletion webapp/site_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,10 @@ def get_tree_from_db(self):
.all()
)
# build tree from repository in case DB table is empty
if not webpages or self._has_incomplete_pages(webpages):
# TODO: Revert this line to `if not webpages ...`
# before merging this PR
# This is only for QA
if True or not webpages or self._has_incomplete_pages(webpages):
tree = self.get_new_tree()
# otherwise, build tree from DB
else:
Expand Down Expand Up @@ -305,6 +308,7 @@ def __create_webpage_for_node__(
webpage.description = node["description"]
webpage.copy_doc_link = node["link"]
webpage.parent_id = parent_id
webpage.ext = node["ext"]
if webpage.status == WebpageStatus.NEW:
webpage.status = WebpageStatus.AVAILABLE

Expand Down
Loading