diff --git a/README.md b/README.md index e69de29..c6f4e1d 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,27 @@ +# 網頁管理編輯器 + +# Fork +This project is forked from [vvvebjs](https://github.com/givanz/VvvebJs) + +# 目的 +為了替代Google site可以提供社團或者一些使用者來進行簡易架站(如Publii之類的) +以及希望可以增加Google site不支援的模組如Blog等 +但是由於本人對Python比較熟悉,所以不考慮使用現有的vvvebjs cms套組,而是另外重新撰寫dashboard等 +~~我就是死不用node的傢伙~~ +(其實相當於是預期重製一套更好用一點CMS套組) + +# requirement +uv(python 3.13) + +# 預計功能 +- [x] 中文化 +- [ ] 部落格模組 +- [ ] 帳號管理(註冊登入網站管理器、網站成員管理) +- [ ] 網站管理(頁面管理、模組管理、檔案管理、版本控制) +- [ ] 部署功能(網域配置:dns與tunnel設置?) +- [ ] 網站模板(提供一些預設模板) +- [ ] 網站設定(如網站名稱、網站描述、網站logo等) +- [ ] 自動導覽列模組(根據頁面名稱自動生成導覽列) + +# 招募貢獻 +目前還在開發中,歡迎有興趣的開發者一起參與開發,可以[來信](mailto:hi@l.nudoragon.com)跟我說! \ No newline at end of file diff --git a/main.py b/main.py index e45b286..4a4f092 100644 --- a/main.py +++ b/main.py @@ -62,18 +62,23 @@ def _save_project(slug: str, data: dict[str, Any]) -> None: def _list_pages(slug: str) -> list[str]: - """列出專案目錄下所有 .html 頁面.""" + """列出專案目錄下所有 .html 頁面(包含子資料夾)並回傳相對路徑.""" proj_dir = _project_dir(slug) if not proj_dir.exists(): return [] - return sorted(p.name for p in proj_dir.glob("*.html")) + # 排除專案設定檔、寮本檔等非頁面檔 + return sorted( + str(p.relative_to(proj_dir)).replace("\\", "/") + for p in proj_dir.rglob("*.html") + if p.name != "project.json" + ) def _project_summary(slug: str) -> dict[str, Any]: data = _load_project(slug) pages = _list_pages(slug) proj_dir = _project_dir(slug) - html_files = list(proj_dir.glob("*.html")) + html_files = list(proj_dir.rglob("*.html")) if html_files: last_mod = max(f.stat().st_mtime for f in html_files) last_mod_str = datetime.fromtimestamp(last_mod, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S") @@ -88,10 +93,27 @@ def _project_summary(slug: str) -> dict[str, Any]: } -def _sanitize_file_path(slug: str, filename: str) -> Path | None: - """驗證並回傳安全的檔案路徑(防止路徑遍歷).""" +def _sanitize_file_path(slug: str, filename: str, folder: str = "") -> Path | None: + """驗證並回傳安全的檔案路徑,支援子資料夾(防止路徑遍歷).""" proj_dir = _project_dir(slug).resolve() - target = (proj_dir / Path(filename).name).resolve() + safe_filename = Path(filename).name # 只取最後的檔名,防止路徑注入 + if folder: + safe_folder = str(Path(folder)).lstrip("/\\").replace("..", "") + target = (proj_dir / safe_folder / safe_filename).resolve() + else: + target = (proj_dir / safe_filename).resolve() + if not str(target).startswith(str(proj_dir)): + return None + if target.suffix.lower() != ".html": + return None + return target + + +def _sanitize_rel_path(slug: str, rel_path: str) -> Path | None: + """驗證並回傳安全的相對路徑(支援子資料夾).""" + proj_dir = _project_dir(slug).resolve() + clean = rel_path.lstrip("/\\").replace("..", "") + target = (proj_dir / clean).resolve() if not str(target).startswith(str(proj_dir)): return None if target.suffix.lower() != ".html": @@ -128,18 +150,21 @@ def editor(slug: str) -> str: proj_dir = _project_dir(slug) if not proj_dir.exists(): abort(404) - pages = _list_pages(slug) + pages = _list_pages(slug) # 現在回傳相對路徑列表,如 'index.html', 'sub/about.html' project = _load_project(slug) pages_obj: dict[str, Any] = {} - for page in pages: - name = page.replace(".html", "") + for rel_path in pages: + name = rel_path.replace(".html", "") + stem = Path(rel_path).stem.replace("-", " ").title() + folder_part = name.rsplit("/", 1)[0] if "/" in name else "" pages_obj[name] = { "name": name, - "title": name.replace("-", " ").title(), - "filename": page, - "file": page, - "url": f"/sites/{slug}/{page}", + "title": stem, + "folder": folder_part, + "filename": rel_path, + "file": rel_path, + "url": f"/sites/{slug}/{rel_path}", } if not pages_obj: @@ -279,14 +304,16 @@ def api_save() -> tuple[Response, int] | Response: if action == "rename": file = str(body.get("file", "")).strip() newfile = str(body.get("newfile", "")).strip() + new_title = str(body.get("title", "")).strip() + new_folder = str(body.get("folder", "")).strip() duplicate_str = str(body.get("duplicate", "")).strip().lower() is_duplicate = (duplicate_str == "true") if not file or not newfile: return jsonify({"error": "缺少參數 file 或 newfile"}), 400 - old_path = _sanitize_file_path(slug, file) - new_path = _sanitize_file_path(slug, newfile) + old_path = _sanitize_rel_path(slug, file) + new_path = _sanitize_file_path(slug, newfile, new_folder) if old_path is None or new_path is None: return jsonify({"error": "不合法的頁面名稱"}), 400 @@ -297,6 +324,9 @@ def api_save() -> tuple[Response, int] | Response: if new_path.exists() and old_path != new_path: return jsonify({"error": "目標頁面已存在"}), 409 + # 建立子資料夾(若需要) + new_path.parent.mkdir(parents=True, exist_ok=True) + if is_duplicate: shutil.copy(old_path, new_path) msg = "頁面複製成功" @@ -304,12 +334,19 @@ def api_save() -> tuple[Response, int] | Response: old_path.rename(new_path) msg = "頁面重新命名成功" + # 計算相對於專案目錄的路徑,用於 URL + proj_dir = _project_dir(slug) + rel_path = new_path.relative_to(proj_dir) + page_name = str(rel_path).replace("\\", "/").replace(".html", "") + return jsonify({ "success": True, "ok": True, "message": msg, - "newfile": new_path.name, - "url": f"/sites/{slug}/{new_path.name}" + "newfile": str(rel_path).replace("\\", "/"), + "title": new_title or new_path.stem.replace("-", " ").title(), + "name": page_name, + "url": f"/sites/{slug}/{str(rel_path).replace(chr(92), '/')}" }) # ── 處理刪除頁面 ── @@ -339,19 +376,23 @@ def api_save() -> tuple[Response, int] | Response: start_template_url = str(body.get("startTemplateUrl", "")).strip() if start_template_url: title = str(body.get("title", "")).strip() or "New Page" - # 取得安全的檔名 (只取檔名部分,例如 'about.html') + folder = str(body.get("folder", "")).strip() + # 取得安全的檔名 filename = Path(str(body.get("file", "untitled.html")).strip()).name if not filename.endswith(".html"): filename += ".html" - safe_path = _sanitize_file_path(slug, filename) + safe_path = _sanitize_file_path(slug, filename, folder) if safe_path is None: return jsonify({"error": "不合法的頁面名稱"}), 400 if safe_path.exists(): return jsonify({"error": "頁面已存在"}), 409 - # 解析並複製樣板 (定位在 static/Vvvebjs 目錄下) + # 建立子資料夾(若需要) + safe_path.parent.mkdir(parents=True, exist_ok=True) + + # 解析並複製樣板 template_source = BASE_DIR / "static" / "Vvvebjs" / start_template_url if template_source.exists() and template_source.is_file(): shutil.copy(template_source, safe_path) @@ -359,28 +400,32 @@ def api_save() -> tuple[Response, int] | Response: _copy_blank_template(safe_path) # 回傳 VvvebJS FileManager 所期待的 JSON 格式 - page_name = safe_path.stem + proj_dir = _project_dir(slug) + rel_path = safe_path.relative_to(proj_dir) + page_name = str(rel_path).replace("\\", "/").replace(".html", "") + return jsonify({ "ok": True, "name": page_name, "title": title, - "file": safe_path.name, - "url": f"/sites/{slug}/{safe_path.name}" + "file": str(rel_path).replace("\\", "/"), + "url": f"/sites/{slug}/{str(rel_path).replace(chr(92), '/')}" }) - # 2. 一般儲存頁面請求 + # 2. 一般儲存頁面請求(支援子資料夾路徑) filename = str(body.get("file", "")).strip() html: str = str(body.get("html", "")).strip() if not filename or not html: return jsonify({"error": "缺少必要參數 file / html"}), 400 - safe_path = _sanitize_file_path(slug, filename) + safe_path = _sanitize_rel_path(slug, filename) if safe_path is None: return jsonify({"error": "不合法的檔案路徑"}), 400 + safe_path.parent.mkdir(parents=True, exist_ok=True) safe_path.write_text(html, encoding="utf-8") - return jsonify({"ok": True, "saved": safe_path.name}) + return jsonify({"ok": True, "saved": str(safe_path.relative_to(_project_dir(slug))).replace("\\", "/")}) diff --git a/static/js/my-editor.js b/static/js/my-editor.js index 083d700..cd1439c 100644 --- a/static/js/my-editor.js +++ b/static/js/my-editor.js @@ -332,6 +332,116 @@ window.addEventListener("keydown", keyHandler); } + // ── 三欄位頁面表單對話框 (頁面名稱 / 檔案名稱 / 資料夾) ──────────── + function showModalPageForm(actionTitle, defaultTitle, defaultFile, defaultFolder, callback) { + const existing = document.getElementById("vvveb-custom-modal"); + if (existing) existing.remove(); + + const overlay = document.createElement("div"); + overlay.id = "vvveb-custom-modal"; + Object.assign(overlay.style, { + position: "fixed", top: "0", left: "0", width: "100%", height: "100%", + zIndex: "100000", background: "rgba(10,11,18,0.65)", + backdropFilter: "blur(12px)", display: "flex", + alignItems: "center", justifyContent: "center", + opacity: "0", transition: "opacity 0.25s ease-out" + }); + + const card = document.createElement("div"); + Object.assign(card.style, { + background: "rgba(22,24,38,0.97)", + border: "1px solid rgba(255,255,255,0.08)", + boxShadow: "0 20px 50px rgba(0,0,0,0.6), 0 0 40px rgba(99,102,241,0.1)", + borderRadius: "16px", width: "460px", padding: "2rem", + color: "#f8fafc", fontFamily: "Inter, system-ui, sans-serif", + transform: "scale(0.95)", + transition: "transform 0.25s cubic-bezier(0.34,1.56,0.64,1)", + display: "flex", flexDirection: "column", gap: "1.1rem" + }); + + const fieldStyle = ` + width:100%; background:rgba(13,14,24,0.8); + border:1px solid rgba(255,255,255,0.1); border-radius:8px; + padding:0.6rem 0.85rem; color:#fff; font-size:0.88rem; + outline:none; box-sizing:border-box; transition:border-color 0.2s, box-shadow 0.2s;`; + const labelStyle = `display:block; font-size:0.78rem; color:#94a3b8; margin-bottom:0.3rem; font-weight:500;`; + + card.innerHTML = ` +
+ + + + + ${actionTitle} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
`; + + overlay.appendChild(card); + document.body.appendChild(overlay); + + const titleInput = card.querySelector("#pf-title"); + const fileInput = card.querySelector("#pf-file"); + const folderInput = card.querySelector("#pf-folder"); + const cancelBtn = card.querySelector("#pf-cancel"); + const confirmBtn = card.querySelector("#pf-confirm"); + + // Focus style + [titleInput, fileInput, folderInput].forEach(inp => { + inp.addEventListener("focus", () => { inp.style.borderColor = "#818cf8"; inp.style.boxShadow = "0 0 10px rgba(129,140,248,0.2)"; }); + inp.addEventListener("blur", () => { inp.style.borderColor = "rgba(255,255,255,0.1)"; inp.style.boxShadow = "none"; }); + }); + cancelBtn.addEventListener("mouseover", () => { cancelBtn.style.background = "rgba(255,255,255,0.06)"; }); + cancelBtn.addEventListener("mouseout", () => { cancelBtn.style.background = "transparent"; }); + confirmBtn.addEventListener("mouseover", () => { confirmBtn.style.background = "#4338ca"; }); + confirmBtn.addEventListener("mouseout", () => { confirmBtn.style.background = "#4f46e5"; }); + + // Auto-fill filename from title + titleInput.addEventListener("input", () => { + const slug = titleInput.value.trim().toLowerCase() + .replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/^-+|-+$/g, ""); + if (slug) fileInput.value = slug; + }); + + setTimeout(() => { overlay.style.opacity = "1"; card.style.transform = "scale(1)"; }, 10); + titleInput.focus(); + titleInput.select(); + + function close(confirmed) { + overlay.style.opacity = "0"; + card.style.transform = "scale(0.95)"; + setTimeout(() => { + overlay.remove(); + if (confirmed) { + callback({ title: titleInput.value.trim(), filename: fileInput.value.trim(), folder: folderInput.value.trim() }); + } else { + callback(null); + } + }, 220); + } + + cancelBtn.onclick = () => close(false); + confirmBtn.onclick = () => close(true); + fileInput.onkeydown = titleInput.onkeydown = folderInput.onkeydown = (e) => { + if (e.key === "Enter") close(true); + if (e.key === "Escape") close(false); + }; + } + // ── 覆蓋 Vvveb.Builder.saveAjax ────────────────────────────────── function patchSaveAjax() { if (typeof Vvveb === "undefined" || !Vvveb.Builder) { @@ -501,21 +611,25 @@ }); }; - // 覆蓋重新命名 / 複製頁面 + // 覆蓋重新命名 / 複製頁面(三欄位對話框:頁面名稱、檔案名稱、儲存至資料夾) Vvveb.FileManager.renamePage = function (element, e, duplicate = false) { let page = element.dataset; - showModalPrompt(`請輸入 "${page.file}" 的新檔名:`, page.file, function (newfile) { - if (!newfile) return; + const currentTitle = element.querySelector("label > span")?.textContent?.trim() || page.file.replace(".html",""); + const currentFile = page.file.replace(".html",""); + const action = duplicate ? "複製頁面" : "重新命名頁面"; - // 確保副檔名為 .html - if (!newfile.endsWith(".html")) { - newfile += ".html"; - } + showModalPageForm(action, currentTitle, currentFile, "", function (formData) { + if (!formData) return; + let { title, filename, folder } = formData; + if (!filename) return; + if (!filename.endsWith(".html")) filename += ".html"; const bodyData = { slug: SLUG, file: page.file, - newfile: newfile, + newfile: filename, + title: title, + folder: folder, duplicate: duplicate ? "true" : "false" }; @@ -534,40 +648,32 @@ }) .then((data) => { showToast(`✓ ${data.message}`); - let baseName = data.newfile.replace('.html', ''); - let newName = friendlyName(data.newfile.replace(/.*[\/\\]+/, '')).replace('.html', ''); + const baseName = data.name || data.newfile.replace('.html', ''); + const newTitle = data.title || title || baseName; if (duplicate) { - // 複製頁面:在 FileManager 中加入新頁面 let pageData = Object.assign({}, Vvveb.FileManager.pages[page.page]); pageData["file"] = data.newfile; - pageData["title"] = newName; + pageData["title"] = newTitle; pageData["url"] = data.url; pageData["name"] = baseName; Vvveb.FileManager.addPage(baseName, pageData); } else { - // 重新命名:更新現有節點資訊 const oldPageKey = page.page; Vvveb.FileManager.pages[oldPageKey]["file"] = data.newfile; - Vvveb.FileManager.pages[oldPageKey]["title"] = newName; + Vvveb.FileManager.pages[oldPageKey]["title"] = newTitle; Vvveb.FileManager.pages[oldPageKey]["url"] = data.url; Vvveb.FileManager.pages[oldPageKey]["name"] = baseName; let link = element.querySelector("a.view"); - if (link) { - link.setAttribute("href", data.url); - } - let span = element.querySelector("label > span"); - if (!span) { - span = element.querySelector("span"); - } - if (span) { - span.textContent = newName; - } + if (link) link.setAttribute("href", data.url); + + let span = element.querySelector("label > span") || element.querySelector("span"); + if (span) span.textContent = newTitle; + element.dataset.file = data.newfile; element.dataset.page = baseName; - - // 將 key 重新綁定 + Vvveb.FileManager.pages[baseName] = Vvveb.FileManager.pages[oldPageKey]; if (baseName !== oldPageKey) { delete Vvveb.FileManager.pages[oldPageKey]; @@ -666,6 +772,129 @@ console.log("[my-editor] Vvveb.NewSection.insert patched to prevent layout crashes."); } + // ── 頁面清單樹狀結構 ──────────────────────────────────────────────── + function patchPageTree() { + if (typeof Vvveb === "undefined" || !Vvveb.FileManager) { + setTimeout(patchPageTree, 300); + return; + } + + // 攔截 addPages — 批次新增後重組樹狀結構 + const origAddPages = Vvveb.FileManager.addPages; + if (origAddPages) { + Vvveb.FileManager.addPages = function (pages) { + origAddPages.call(this, pages); + setTimeout(buildPageTree, 80); + }; + } + + // 攔截 addPage — 單筆新增(Rename/Duplicate)後重組 + const origAddPage = Vvveb.FileManager.addPage; + if (origAddPage) { + Vvveb.FileManager.addPage = function (name, page, ...rest) { + const result = origAddPage.call(this, name, page, ...rest); + setTimeout(buildPageTree, 80); + return result; + }; + } + + console.log("[my-editor] Page tree patch ready."); + } + + function buildPageTree() { + // 找到頁面清單容器(支援多種可能的 selector) + const list = ( + document.querySelector("#file-manager .files") || + document.querySelector(".file-manager .files") || + document.querySelector("#file-manager ul") || + (() => { + const li = document.querySelector("li[data-file]"); + return li ? li.closest("ul") : null; + })() + ); + if (!list) return; + + // 取得所有頁面 li(不包含我們建立的 folder-node) + const allPageItems = [...list.querySelectorAll("li[data-file]:not(.folder-node-item)")]; + if (allPageItems.length === 0) return; + + // 移除舊的資料夾節點(避免重複) + list.querySelectorAll(".folder-node").forEach(n => n.remove()); + + // 分組:根頁面 vs 子資料夾頁面 + const rootItems = []; + const folderMap = {}; // folderName -> [li] + + allPageItems.forEach(li => { + const pageKey = li.dataset.page || ""; + if (pageKey.includes("/")) { + const folderName = pageKey.split("/").slice(0, -1).join("/"); + if (!folderMap[folderName]) folderMap[folderName] = []; + folderMap[folderName].push(li); + li.remove(); // 先從扁平清單移除,稍後放進資料夾節點 + } else { + rootItems.push(li); + } + }); + + // 每個資料夾建一個折疊節點並加到清單尾端 + for (const folderName in folderMap) { + const folderNode = createFolderNode(folderName, folderMap[folderName]); + list.appendChild(folderNode); + } + } + + function createFolderNode(folderPath, childItems) { + const displayName = folderPath.split("/").pop() + .replace(/-/g, " ") + .replace(/\b\w/g, c => c.toUpperCase()); + + const li = document.createElement("li"); + li.className = "folder-node"; + li.dataset.folder = folderPath; + li.style.cssText = "list-style:none;"; + + li.innerHTML = ` +
+ + + + + + + ${displayName} +
+ `; + + const childrenUl = li.querySelector(".folder-children"); + childItems.forEach(child => { + child.classList.add("folder-node-item"); + childrenUl.appendChild(child); + }); + + // 折疊切換 + const header = li.querySelector(".folder-header"); + const chevron = li.querySelector(".chevron"); + let collapsed = false; + + header.addEventListener("mouseenter", () => { header.style.background = "rgba(99,102,241,0.08)"; }); + header.addEventListener("mouseleave", () => { header.style.background = ""; }); + header.addEventListener("click", (e) => { + if (e.target.closest("li[data-file]")) return; + collapsed = !collapsed; + childrenUl.style.display = collapsed ? "none" : ""; + chevron.style.transform = collapsed ? "rotate(-90deg)" : ""; + }); + + return li; + } + // ── 動態中文化 VvvebJS 元件與區塊 ───────────────────────────────── function patchI18n() { if (typeof Vvveb === "undefined" || !Vvveb.ComponentsGroup || Object.keys(Vvveb.ComponentsGroup).length === 0) { @@ -979,6 +1208,7 @@ patchSaveAjax(); patchFileManager(); patchNewSection(); + patchPageTree(); patchI18n(); enableSaveBtn(); }); @@ -986,6 +1216,7 @@ patchSaveAjax(); patchFileManager(); patchNewSection(); + patchPageTree(); patchI18n(); enableSaveBtn(); } diff --git a/templates/editor.html b/templates/editor.html index 880e2d1..de87321 100644 --- a/templates/editor.html +++ b/templates/editor.html @@ -2052,8 +2052,8 @@
- +
@@ -2062,7 +2062,7 @@
-
@@ -2083,8 +2083,8 @@
- +
@@ -2287,6 +2287,21 @@ Vvveb.FileManager.addPages(pages); Vvveb.FileManager.loadPage(pages[firstPage]["name"]); Vvveb.Gui.toggleRightColumn(false); Vvveb.Breadcrumb.init(); + +// ── 新增頁面對話框:頁面名稱自動同步至檔案名稱 ── +document.addEventListener("DOMContentLoaded", function () { + var titleInput = document.getElementById("new-page-title"); + var fileInput = document.getElementById("new-page-file"); + if (titleInput && fileInput) { + titleInput.addEventListener("input", function () { + var slug = titleInput.value.trim().toLowerCase() + .replace(/[^\w\s-]/g, "") + .replace(/[\s_]+/g, "-") + .replace(/^-+|-+$/g, ""); + if (slug) fileInput.value = slug + ".html"; + }); + } +}); + + + + + + + +
+
+
+

Bootstrap 5 start page

+

Start by dragging components to page or double click to edit text

+
+
+
+ + + \ No newline at end of file diff --git a/websites/my-website/my-page4.html b/websites/my-website/my-page4.html new file mode 100644 index 0000000..ab66e65 --- /dev/null +++ b/websites/my-website/my-page4.html @@ -0,0 +1,754 @@ + + + +Crafto - The Multipurpose HTML5 Template + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+
+
+ +
+
+
+
+
+ +conference + + +Get tickets now + + + +
+24 December 2021, New york +
+
+
+
+
+ + +
+
+
+ +
+
+
+

01

+
+
+Keynote speakers +
+
+
+ + +
+
+
+

02

+
+
+Hygienic brunch +
+
+
+ + +
+
+
+

03

+
+
+Personal integrity +
+
+
+ +
+
+
+ + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+35 +International speakers +
+
+
+
+
+
+
+
+

+About conference +

+ +
+
+
+ +
+
+Day long meetup +
+
Lorem ipsum dolor consectetur eiusmod tempor incididunt labore exercitation.
+
+
+ + +
+
+
+ +
+
+Meet the leader +
+
Lorem ipsum dolor consectetur eiusmod tempor incididunt labore exercitation.
+
+
+ + + +About conference + + + +
+
+
+
+ + +
+
+
+
+

Industry experts

+
+
+

Lorem ipsum dolor to consectetur adipiscing elit eiusmod tempor incididunt labore.

+
+
+
+ +
+
+ +
+
Lorem ipsum dolor consecte eiusmod.
+ +
+
+Michal ruheen +Economist +
+ + +
+
+ +
+
Lorem ipsum dolor consecte eiusmod.
+ +
+
+Jessica dover +Geologist +
+ + +
+
+ +
+
Lorem ipsum dolor consecte eiusmod.
+ +
+
+Matthew taylor +Psychologist +
+ + +
+
+ +
+
Lorem ipsum dolor consecte eiusmod.
+ +
+
+Rodney stratton +Psychologist +
+ +
+
+
+ + +
+
+
+ +
+
+

Event schedule

+

Lorem ipsum dolor consectetur eiusmod tempor incididunt labore exercitation tempor.

+ + +Download schedule + + + +
+
+ + +
+
+
+
Friday, Dec 24
+

Psychologist - John parker
10:00 AM to 12:30 PM

+
+

Sociology - Herman miller
02:00 PM to 04:30 PM

+
+

Geologist - Jeremy dupont
05:00 PM to 07:30 PM

+01 +
+
+
+
+ + +
+
+
+
Saturday, Dec 25
+

Economy - Michal ruheen
10:00 AM to 12:30 PM

+
+

Engineer - Jessica dover
02:00 PM to 04:30 PM

+
+

Psychologist - John parker
05:00 PM to 07:30 PM

+02 +
+
+
+
+ + +
+
+
+
Sunday, Dec 26
+

Biologist- Saleena fountain
10:00 AM to 12:30 PM

+
+

Secretary- Paulina morris
02:00 PM to 04:30 PM

+
+

Politician - Wendaya royin
05:00 PM to 07:30 PM

+03 +
+
+
+
+ +
+
+
+ + +
+
+
+
+

Conference packages

+

Lorem ipsum dolor sit amet consectetur adipiscing elit do eiusmod tempor incididunt labore et dolore magna ut enim.

+ + +Pricing plans + + + +
+
+
+ +
+
+
+ +
Personal
+

$250

+
+
+
    +
  • Regular seats
  • +
  • Snacks and brunch
  • +
  • Event certificate
  • +
+
+ +
+
+ + +
+ +
+ +
+
+
+
+
+
+
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+
+
+ + +
+
+
+
+ +
+
+
+ +

Hurry up! Don't waste time important event.

+ + +Get tickets now + + + +
+
+
+
+ + +
+
+
+
+

Loved by our past attendees

+
+ +
+
+ +
+
+
+
+
+
+ +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Herman miller
+
Founder
+
+
+
+
+ + +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Leonel mooney
+
Manager
+
+
+
+
+ + +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Matthew taylor
+
Expert
+
+
+
+
+ + +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Shoko mugikura
+
Manager
+
+
+
+
+ + +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Jacob kalling
+
Designer
+
+
+
+
+ + +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Alexander harad
+
Manager
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+ + + + + +
+ +Scroll + +
+ + + + + + + diff --git a/websites/my-website/temp/my-page5.html b/websites/my-website/temp/my-page5.html new file mode 100644 index 0000000..ab66e65 --- /dev/null +++ b/websites/my-website/temp/my-page5.html @@ -0,0 +1,754 @@ + + + +Crafto - The Multipurpose HTML5 Template + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+
+
+ +
+
+
+
+
+ +conference + + +Get tickets now + + + +
+24 December 2021, New york +
+
+
+
+
+ + +
+
+
+ +
+
+
+

01

+
+
+Keynote speakers +
+
+
+ + +
+
+
+

02

+
+
+Hygienic brunch +
+
+
+ + +
+
+
+

03

+
+
+Personal integrity +
+
+
+ +
+
+
+ + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+35 +International speakers +
+
+
+
+
+
+
+
+

+About conference +

+ +
+
+
+ +
+
+Day long meetup +
+
Lorem ipsum dolor consectetur eiusmod tempor incididunt labore exercitation.
+
+
+ + +
+
+
+ +
+
+Meet the leader +
+
Lorem ipsum dolor consectetur eiusmod tempor incididunt labore exercitation.
+
+
+ + + +About conference + + + +
+
+
+
+ + +
+
+
+
+

Industry experts

+
+
+

Lorem ipsum dolor to consectetur adipiscing elit eiusmod tempor incididunt labore.

+
+
+
+ +
+
+ +
+
Lorem ipsum dolor consecte eiusmod.
+ +
+
+Michal ruheen +Economist +
+ + +
+
+ +
+
Lorem ipsum dolor consecte eiusmod.
+ +
+
+Jessica dover +Geologist +
+ + +
+
+ +
+
Lorem ipsum dolor consecte eiusmod.
+ +
+
+Matthew taylor +Psychologist +
+ + +
+
+ +
+
Lorem ipsum dolor consecte eiusmod.
+ +
+
+Rodney stratton +Psychologist +
+ +
+
+
+ + +
+
+
+ +
+
+

Event schedule

+

Lorem ipsum dolor consectetur eiusmod tempor incididunt labore exercitation tempor.

+ + +Download schedule + + + +
+
+ + +
+
+
+
Friday, Dec 24
+

Psychologist - John parker
10:00 AM to 12:30 PM

+
+

Sociology - Herman miller
02:00 PM to 04:30 PM

+
+

Geologist - Jeremy dupont
05:00 PM to 07:30 PM

+01 +
+
+
+
+ + +
+
+
+
Saturday, Dec 25
+

Economy - Michal ruheen
10:00 AM to 12:30 PM

+
+

Engineer - Jessica dover
02:00 PM to 04:30 PM

+
+

Psychologist - John parker
05:00 PM to 07:30 PM

+02 +
+
+
+
+ + +
+
+
+
Sunday, Dec 26
+

Biologist- Saleena fountain
10:00 AM to 12:30 PM

+
+

Secretary- Paulina morris
02:00 PM to 04:30 PM

+
+

Politician - Wendaya royin
05:00 PM to 07:30 PM

+03 +
+
+
+
+ +
+
+
+ + +
+
+
+
+

Conference packages

+

Lorem ipsum dolor sit amet consectetur adipiscing elit do eiusmod tempor incididunt labore et dolore magna ut enim.

+ + +Pricing plans + + + +
+
+
+ +
+
+
+ +
Personal
+

$250

+
+
+
    +
  • Regular seats
  • +
  • Snacks and brunch
  • +
  • Event certificate
  • +
+
+ +
+
+ + +
+ +
+ +
+
+
+
+
+
+
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+
+
+ + +
+
+
+
+ +
+
+
+ +

Hurry up! Don't waste time important event.

+ + +Get tickets now + + + +
+
+
+
+ + +
+
+
+
+

Loved by our past attendees

+
+ +
+
+ +
+
+
+
+
+
+ +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Herman miller
+
Founder
+
+
+
+
+ + +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Leonel mooney
+
Manager
+
+
+
+
+ + +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Matthew taylor
+
Expert
+
+
+
+
+ + +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Shoko mugikura
+
Manager
+
+
+
+
+ + +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Jacob kalling
+
Designer
+
+
+
+
+ + +
+
+

Lorem ipsum dolor is elit adipiscing do eiusmod tempor labore dolore.

+
+ +
+
Alexander harad
+
Manager
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+ + + + + +
+ +Scroll + +
+ + + + + + + diff --git a/websites/my-website/temp/new.html b/websites/my-website/temp/new.html new file mode 100644 index 0000000..8821b1c --- /dev/null +++ b/websites/my-website/temp/new.html @@ -0,0 +1,33 @@ + + + + + + + + My page + + + + + + + + + +
+
+
+

Bootstrap 5 start page

+

Start by dragging components to page or double click to edit text

+
+
+
+ + diff --git a/websites/my-website/test.html b/websites/my-website/test.html new file mode 100644 index 0000000..8821b1c --- /dev/null +++ b/websites/my-website/test.html @@ -0,0 +1,33 @@ + + + + + + + + My page + + + + + + + + + +
+
+
+

Bootstrap 5 start page

+

Start by dragging components to page or double click to edit text

+
+
+
+ +