Merge branch 'main' into dev
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,4 @@ | |||||||
| node_modules | node_modules | ||||||
| src/server/public/dist | src/server/public/dist | ||||||
|  | src/server/shrinko8 | ||||||
| .env | .env | ||||||
							
								
								
									
										101
									
								
								src/client/GamePage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/client/GamePage.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | import { useNavigate, useParams } from "react-router-dom" | ||||||
|  | import { Pico8Console } from "./pico8-client/Pico8Console"; | ||||||
|  | import { useEffect, useState } from "react"; | ||||||
|  | import { DbRelease } from "../server/dbal/dbal"; | ||||||
|  | import { css } from "@emotion/css"; | ||||||
|  |  | ||||||
|  | type Info = { | ||||||
|  |     release: DbRelease | null; | ||||||
|  |     versions: string[]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const GamePage = () => { | ||||||
|  | 	const {author, slug, version} = useParams(); | ||||||
|  | 	const navigate = useNavigate(); | ||||||
|  | 	const [info, setInfo] = useState<Info | null>(null); | ||||||
|  |  | ||||||
|  | 	useEffect(() => { | ||||||
|  | 		const fetchInfo = async () => { | ||||||
|  | 			let url = `/api/release?author=${author}&slug=${slug}`; | ||||||
|  | 			if (version) { | ||||||
|  | 				url += `&version=${version.slice(1)}`; | ||||||
|  | 			} | ||||||
|  | 			const information = await fetch(url); | ||||||
|  | 			const json = await information.json(); | ||||||
|  | 			setInfo(json); | ||||||
|  | 		} | ||||||
|  | 		fetchInfo(); | ||||||
|  | 	}, [setInfo, author, slug, version]); | ||||||
|  |  | ||||||
|  | 	if (!info) { | ||||||
|  | 		return ( | ||||||
|  | 			<div className={` | ||||||
|  | 				min-height: 100vh; | ||||||
|  | 			`}> | ||||||
|  | 				LOADING... | ||||||
|  | 			</div> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (!info.release) { | ||||||
|  | 		return ( | ||||||
|  | 			<div className={` | ||||||
|  | 				min-height: 100vh; | ||||||
|  | 			`}> | ||||||
|  | 				NOT FOUND | ||||||
|  | 			</div> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ( | ||||||
|  | 		<div className={css` | ||||||
|  | 			min-height: 100vh; | ||||||
|  | 			background-color: hsl(230, 10%, 10%); | ||||||
|  | 			color: white; | ||||||
|  | 		`}> | ||||||
|  | 			<div className={css` | ||||||
|  | 				margin: auto; | ||||||
|  | 				width: max-content; | ||||||
|  | 				max-inline-size: 66ch; | ||||||
|  | 				padding: 1.5em; | ||||||
|  | 				display: flex; | ||||||
|  | 				flex-direction: column; | ||||||
|  | 				gap: 1em; | ||||||
|  | 			`}> | ||||||
|  | 				<div> | ||||||
|  | 					<h1>{info.release.manifest.title ?? slug!.split("-").map(word => word[0].toUpperCase()+word.slice(1)).join(" ")}</h1> | ||||||
|  | 					<h2>by {info.release.author}</h2> | ||||||
|  | 				</div> | ||||||
|  | 				<div className={css` | ||||||
|  | 					width: 512px; | ||||||
|  | 					max-width: 100%; | ||||||
|  | 					margin: auto; | ||||||
|  | 				`}> | ||||||
|  | 					<div className={css` | ||||||
|  | 						border: 2px solid transparent; | ||||||
|  | 						&:focus-within { | ||||||
|  | 							border: 2px solid limegreen; | ||||||
|  | 						} | ||||||
|  | 					`}> | ||||||
|  | 						<Pico8Console carts={info.release.carts} /> | ||||||
|  | 					</div> | ||||||
|  | 					<div className={css` | ||||||
|  | 						display: flex; | ||||||
|  | 						justify-content: end; | ||||||
|  | 					`}> | ||||||
|  | 						Version: <select defaultValue={info.release.version} onChange={(ev) => navigate(`/u/${author}/${slug}/v${ev.target.value}`)}> | ||||||
|  | 							{ | ||||||
|  | 								[...info.versions].reverse().map(v => ( | ||||||
|  | 									<option key={v} value={v}>{v}</option> | ||||||
|  | 								)) | ||||||
|  | 							} | ||||||
|  | 						</select> | ||||||
|  | 					</div> | ||||||
|  | 				</div> | ||||||
|  | 				{/* <div> | ||||||
|  | 					<p>This is a paragraph about this game. It is a cool game. And a cool website to play it on. It automagically connects from GitHub.</p> | ||||||
|  | 				</div> */} | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								src/client/HomePage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/client/HomePage.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | export const HomePage = () => { | ||||||
|  | 	return <div>Welcome to Picobook!</div> | ||||||
|  | } | ||||||
| @@ -1,15 +1,17 @@ | |||||||
| import { css } from "@emotion/css"; | import { css } from "@emotion/css"; | ||||||
| import { Pico8Console } from "./pico8-client/Pico8Console"; | import { Pico8Console } from "./pico8-client/Pico8Console"; | ||||||
| import testcarts from "./testcarts"; | import testcarts from "./testcarts"; | ||||||
|  | import { Routing } from "./routing"; | ||||||
|  |  | ||||||
| const App = (props: {}) => { | const App = (props: {}) => { | ||||||
| 	return ( | 	return ( | ||||||
| 		<div className={css` | 		<Routing/> | ||||||
| 			min-height: 100vh; | 		// <div className={css` | ||||||
| 		`}> | 		// 	min-height: 100vh; | ||||||
| 			<h1>Picobook</h1> | 		// `}> | ||||||
| 			<Pico8Console carts={testcarts.carts} /> | 		// 	<h1>Picobook</h1> | ||||||
| 		</div> | 		// 	<Pico8Console carts={testcarts.carts} /> | ||||||
|  | 		// </div> | ||||||
| 	); | 	); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,16 +8,27 @@ type Pico8ConsoleImperatives = { | |||||||
|  |  | ||||||
| export const Pico8Console = forwardRef((props: { carts: PicoCart[] }, forwardedRef: ForwardedRef<Pico8ConsoleImperatives>) => { | export const Pico8Console = forwardRef((props: { carts: PicoCart[] }, forwardedRef: ForwardedRef<Pico8ConsoleImperatives>) => { | ||||||
| 	const {carts} = props; | 	const {carts} = props; | ||||||
|  | 	const [playing, setPlaying] = useState(false); | ||||||
| 	const ref = useRef<HTMLDivElement>(null); | 	const ref = useRef<HTMLDivElement>(null); | ||||||
| 	const [handle, setHandle] = useState<PicoPlayerHandle | null>(null); | 	const [handle, setHandle] = useState<PicoPlayerHandle | null>(null); | ||||||
| 	const attachConsole = useCallback(async () => { | 	const attachConsole = useCallback(async () => { | ||||||
| 		const picoConsole = await makePicoConsole({ | 		const picoConsole = await makePicoConsole({ | ||||||
| 			carts, | 			carts, | ||||||
| 		}); | 		}); | ||||||
|  | 		picoConsole.canvas.tabIndex=0; | ||||||
| 		if (ref.current) { | 		if (ref.current) { | ||||||
| 			ref.current.appendChild(picoConsole.canvas); | 			ref.current.appendChild(picoConsole.canvas); | ||||||
|  | 			picoConsole.canvas.focus(); | ||||||
| 		} | 		} | ||||||
| 		setHandle(picoConsole); | 		setHandle(picoConsole); | ||||||
|  | 		picoConsole.canvas.addEventListener('keydown',(event) => { | ||||||
|  | 			if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) { | ||||||
|  | 				event.preventDefault(); | ||||||
|  | 			} | ||||||
|  | 		}, {passive: false}); | ||||||
|  | 		picoConsole.canvas.addEventListener('click', () => { | ||||||
|  | 			picoConsole.canvas.focus(); | ||||||
|  | 		}) | ||||||
| 	}, [carts]); | 	}, [carts]); | ||||||
| 	useImperativeHandle(forwardedRef, () => ({ | 	useImperativeHandle(forwardedRef, () => ({ | ||||||
| 		getPicoConsoleHandle() { | 		getPicoConsoleHandle() { | ||||||
| @@ -25,8 +36,37 @@ export const Pico8Console = forwardRef((props: { carts: PicoCart[] }, forwardedR | |||||||
| 		} | 		} | ||||||
| 	}), [handle]); | 	}), [handle]); | ||||||
| 	useEffect(() => { | 	useEffect(() => { | ||||||
| 		attachConsole(); | 		if (playing) { | ||||||
| 	}, [attachConsole]); | 			attachConsole(); | ||||||
|  | 			return () => { | ||||||
|  | 				if (ref.current) { | ||||||
|  | 					ref.current.innerHTML = ""; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}, [playing, attachConsole]); | ||||||
|  | 	if (!playing) { | ||||||
|  | 		return ( | ||||||
|  | 			<div | ||||||
|  | 				ref={ref} | ||||||
|  | 				className={css` | ||||||
|  | 					width: 100%; | ||||||
|  | 					height: 100%; | ||||||
|  | 					display: flex; | ||||||
|  | 					align-items: center; | ||||||
|  | 					justify-content: center; | ||||||
|  | 					aspect-ratio: 1; | ||||||
|  | 					background-color: black; | ||||||
|  | 					color: white; | ||||||
|  | 					cursor: pointer; | ||||||
|  | 				`} | ||||||
|  | 				tabIndex={0} | ||||||
|  | 				onClick={() => {setPlaying(true)}} | ||||||
|  | 			> | ||||||
|  | 				Play! | ||||||
|  | 			</div> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
| 	return ( | 	return ( | ||||||
| 		<div ref={ref} className={css` | 		<div ref={ref} className={css` | ||||||
| 			width: 100%; | 			width: 100%; | ||||||
|   | |||||||
| @@ -18435,6 +18435,7 @@ use chrome, FireFox or Internet Explorer 11`); | |||||||
| var require_veryRawRenderCart = __commonJS((exports, module) => { | var require_veryRawRenderCart = __commonJS((exports, module) => { | ||||||
|   var __dirname = "/home/dylan/repos/picobook/src/client/pico8-client"; |   var __dirname = "/home/dylan/repos/picobook/src/client/pico8-client"; | ||||||
|   window.P8 = function(Module, cartNames, cartDatas) { |   window.P8 = function(Module, cartNames, cartDatas) { | ||||||
|  |     const codo_textarea_el = Module.codo_textarea || document.getElementById("codo_textarea"); | ||||||
|     let p8_touch_detected; |     let p8_touch_detected; | ||||||
|     let p8_dropped_cart; |     let p8_dropped_cart; | ||||||
|     let p8_dropped_cart_name; |     let p8_dropped_cart_name; | ||||||
| @@ -19390,7 +19391,7 @@ var require_veryRawRenderCart = __commonJS((exports, module) => { | |||||||
|     }, function() { |     }, function() { | ||||||
|       if (typeof codo_key_buffer === "undefined") |       if (typeof codo_key_buffer === "undefined") | ||||||
|         codo_key_buffer = []; |         codo_key_buffer = []; | ||||||
|       document.addEventListener("keydown", function(e) { |       Module["canvas"].addEventListener("keydown", function(e) { | ||||||
|         var val = -1; |         var val = -1; | ||||||
|         if (e.key.length == 1) { |         if (e.key.length == 1) { | ||||||
|           val = e.key.charCodeAt(0); |           val = e.key.charCodeAt(0); | ||||||
| @@ -19406,7 +19407,7 @@ var require_veryRawRenderCart = __commonJS((exports, module) => { | |||||||
|           if (val == -1) { |           if (val == -1) { | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|         var el2 = document.getElementById("codo_textarea"); |         var el2 = codo_textarea_el; | ||||||
|         codo_key_buffer.push(val); |         codo_key_buffer.push(val); | ||||||
|       }); |       }); | ||||||
|     }, function() { |     }, function() { | ||||||
| @@ -19515,7 +19516,7 @@ var require_veryRawRenderCart = __commonJS((exports, module) => { | |||||||
|     }, function() { |     }, function() { | ||||||
|       if (document.hidden) |       if (document.hidden) | ||||||
|         return 0; |         return 0; | ||||||
|       el = typeof codo_textarea === "undefined" ? document.getElementById("codo_textarea") : codo_textarea; |       el = typeof codo_textarea === "undefined" ? codo_textarea_el : codo_textarea; | ||||||
|       if (el && el == document.activeElement) |       if (el && el == document.activeElement) | ||||||
|         return 1; |         return 1; | ||||||
|       el = document.activeElement; |       el = document.activeElement; | ||||||
| @@ -19529,13 +19530,13 @@ var require_veryRawRenderCart = __commonJS((exports, module) => { | |||||||
|       } |       } | ||||||
|       return 0; |       return 0; | ||||||
|     }, function() { |     }, function() { | ||||||
|       el = typeof codo_textarea === "undefined" ? document.getElementById("codo_textarea") : codo_textarea; |       el = typeof codo_textarea === "undefined" ? codo_textarea_el : codo_textarea; | ||||||
|       if (el && el.style.display != "none") { |       if (el && el.style.display != "none") { | ||||||
|         el.focus(); |         el.focus(); | ||||||
|         el.select(); |         el.select(); | ||||||
|       } |       } | ||||||
|     }, function() { |     }, function() { | ||||||
|       el = typeof codo_textarea === "undefined" ? document.getElementById("codo_textarea") : codo_textarea; |       el = typeof codo_textarea === "undefined" ? codo_textarea_el : codo_textarea; | ||||||
|       if (el && el.style.display != "none") { |       if (el && el.style.display != "none") { | ||||||
|         el.select(); |         el.select(); | ||||||
|       } |       } | ||||||
| @@ -19550,7 +19551,7 @@ var require_veryRawRenderCart = __commonJS((exports, module) => { | |||||||
|     }, function() { |     }, function() { | ||||||
|       Module["canvas"].exitPointerLock(); |       Module["canvas"].exitPointerLock(); | ||||||
|     }, function() { |     }, function() { | ||||||
|       el = typeof codo_textarea === "undefined" ? document.getElementById("codo_textarea") : codo_textarea; |       el = typeof codo_textarea === "undefined" ? codo_textarea_el : codo_textarea; | ||||||
|       if (el) { |       if (el) { | ||||||
|       } |       } | ||||||
|     }, function() { |     }, function() { | ||||||
| @@ -19558,21 +19559,21 @@ var require_veryRawRenderCart = __commonJS((exports, module) => { | |||||||
|     }, function($0, $1) { |     }, function($0, $1) { | ||||||
|       _codo_str_out = Module.UTF8ToString($0, $1); |       _codo_str_out = Module.UTF8ToString($0, $1); | ||||||
|     }, function() { |     }, function() { | ||||||
|       el = typeof codo_textarea === "undefined" ? document.getElementById("codo_textarea") : codo_textarea; |       el = typeof codo_textarea === "undefined" ? codo_textarea_el : codo_textarea; | ||||||
|       if (el) { |       if (el) { | ||||||
|         el.value = _codo_str_out; |         el.value = _codo_str_out; | ||||||
|         return 0; |         return 0; | ||||||
|       } else |       } else | ||||||
|         return 1; |         return 1; | ||||||
|     }, function() { |     }, function() { | ||||||
|       el = typeof codo_textarea === "undefined" ? document.getElementById("codo_textarea") : codo_textarea; |       el = typeof codo_textarea === "undefined" ? codo_textarea_el : codo_textarea; | ||||||
|       if (el && el.style.display == "none" && (typeof p8_touch_detected === "undefined" || !p8_touch_detected)) { |       if (el && el.style.display == "none" && (typeof p8_touch_detected === "undefined" || !p8_touch_detected)) { | ||||||
|         el.style.display = ""; |         el.style.display = ""; | ||||||
|         el.focus(); |         el.focus(); | ||||||
|         el.select(); |         el.select(); | ||||||
|       } |       } | ||||||
|     }, function() { |     }, function() { | ||||||
|       el = typeof codo_textarea === "undefined" ? document.getElementById("codo_textarea") : codo_textarea; |       el = typeof codo_textarea === "undefined" ? codo_textarea_el : codo_textarea; | ||||||
|       if (el && el.style.display != "none" && el.value != "") { |       if (el && el.style.display != "none" && el.value != "") { | ||||||
|         _codo_text_value = el.value; |         _codo_text_value = el.value; | ||||||
|         return 1; |         return 1; | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import "./build/veryRawRenderCart.js"; | |||||||
|  |  | ||||||
| export type PicoBool = 0 | 1; | export type PicoBool = 0 | 1; | ||||||
|  |  | ||||||
| export type RenderCart = (Module: {canvas: HTMLCanvasElement}, cartNames: string[], cartDatas: number[][], audioContext: AudioContext) => { | export type RenderCart = (Module: {canvas: HTMLCanvasElement, codo_textarea?: HTMLTextAreaElement}, cartNames: string[], cartDatas: number[][], audioContext: AudioContext) => { | ||||||
| 	p8_touch_detected?: PicoBool; | 	p8_touch_detected?: PicoBool; | ||||||
| 	p8_dropped_cart?: string; | 	p8_dropped_cart?: string; | ||||||
| 	p8_dropped_cart_name?: string; | 	p8_dropped_cart_name?: string; | ||||||
|   | |||||||
| @@ -80,12 +80,18 @@ const getRom = async (cart: PicoCart) => { | |||||||
|  |  | ||||||
| export const makePicoConsole = async (props: { | export const makePicoConsole = async (props: { | ||||||
| 	canvas?: HTMLCanvasElement; | 	canvas?: HTMLCanvasElement; | ||||||
|  | 	codoTextarea?: HTMLTextAreaElement; | ||||||
| 	audioContext?: AudioContext; | 	audioContext?: AudioContext; | ||||||
| 	carts: PicoCart[]; | 	carts: PicoCart[]; | ||||||
| }): Promise<PicoPlayerHandle> => { | }): Promise<PicoPlayerHandle> => { | ||||||
| 	const {carts, canvas = document.createElement("canvas"), audioContext = new AudioContext()} = props; | 	const {carts, canvas = document.createElement("canvas"), codoTextarea = document.createElement("textarea"), audioContext = new AudioContext()} = props; | ||||||
| 	canvas.style.imageRendering = "pixelated"; | 	canvas.style.imageRendering = "pixelated"; | ||||||
| 	const Module = {canvas}; | 	codoTextarea.style.display="none"; | ||||||
|  | 	codoTextarea.style.position="fixed"; | ||||||
|  | 	codoTextarea.style.left="-9999px"; | ||||||
|  | 	codoTextarea.style.height="0px"; | ||||||
|  | 	codoTextarea.style.overflow="hidden"; | ||||||
|  | 	const Module = {canvas, keyboardListeningElement: canvas}; | ||||||
| 	const cartsDatas = await Promise.all(carts.map(cart => getRom(cart))); | 	const cartsDatas = await Promise.all(carts.map(cart => getRom(cart))); | ||||||
| 	const handle = rawRenderCart(Module, carts.map(cart => cart.name), cartsDatas, audioContext); | 	const handle = rawRenderCart(Module, carts.map(cart => cart.name), cartsDatas, audioContext); | ||||||
| 	handle.pico8_state = {}; | 	handle.pico8_state = {}; | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										37
									
								
								src/client/routing.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/client/routing.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | import { Outlet, RouterProvider, ScrollRestoration, createBrowserRouter, redirect } from "react-router-dom" | ||||||
|  | import { HomePage } from "./HomePage"; | ||||||
|  | import { GamePage } from "./GamePage"; | ||||||
|  |  | ||||||
|  | const RouteRoot = () => { | ||||||
|  | 	return <> | ||||||
|  | 		{/* <Nav> */} | ||||||
|  | 			<ScrollRestoration /> | ||||||
|  | 			<Outlet/> | ||||||
|  | 		{/* </Nav> */} | ||||||
|  | 	</> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const router = createBrowserRouter([ | ||||||
|  | 	{ | ||||||
|  | 		element: <RouteRoot/>, | ||||||
|  | 		children: [ | ||||||
|  | 			{ | ||||||
|  | 				path: "/", | ||||||
|  | 				element: <HomePage/>, | ||||||
|  | 				// loader: () => { | ||||||
|  | 				// 	return redirect("/megachat"); | ||||||
|  | 				// } | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				path: "/u/:author/:slug", | ||||||
|  | 				element: <GamePage/>, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				path: "/u/:author/:slug/:version", | ||||||
|  | 				element: <GamePage/>, | ||||||
|  | 			}, | ||||||
|  | 		], | ||||||
|  | 	}, | ||||||
|  | ]); | ||||||
|  |  | ||||||
|  | export const Routing = () => <RouterProvider router={router} />; | ||||||
| @@ -24,7 +24,13 @@ server.register(fastifyStatic, { | |||||||
|  |  | ||||||
| routeList.forEach(firRoute => { | routeList.forEach(firRoute => { | ||||||
| 	server.route(route(firRoute)); | 	server.route(route(firRoute)); | ||||||
| }) | }); | ||||||
|  |  | ||||||
|  | server.setNotFoundHandler((req, res) => { | ||||||
|  | 	if (!req.url.startsWith("/api")) { | ||||||
|  | 		res.sendFile('index.html'); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  |  | ||||||
| // Run the server! | // Run the server! | ||||||
| try { | try { | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html> | <html> | ||||||
| 	<head> | 	<head> | ||||||
| 		<script src="dist/index.js" type="module"></script> | 		<script src="/dist/index.js" type="module"></script> | ||||||
| 		<style> | 		<style> | ||||||
| 			:root { | 			:root { | ||||||
| 				--measure: 64ch; | 				--measure: 64ch; | ||||||
|   | |||||||
| @@ -1120,7 +1120,22 @@ canvas{ | |||||||
|  |  | ||||||
| <!-- Add content below the cart here --> | <!-- Add content below the cart here --> | ||||||
|  |  | ||||||
|  | <div> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | Content here<br/> | ||||||
|  | <input type="text"/> | ||||||
|  | 	</div> | ||||||
|  |  | ||||||
|  |  | ||||||
| </div> <!-- body_0 --> | </div> <!-- body_0 --> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 dylan
					dylan