Compare commits
No commits in common. "6478f546890ff8cb04bf8e6b97cef63777f01427" and "8f757af9de07926dcbf12e11a963706a3cba2335" have entirely different histories.
6478f54689
...
8f757af9de
81
Main.gd
81
Main.gd
@ -1,81 +0,0 @@
|
|||||||
extends Control
|
|
||||||
|
|
||||||
const mime_types: Dictionary = {
|
|
||||||
"html": "text/html",
|
|
||||||
"htm": "text/html",
|
|
||||||
"md": "text/plain",
|
|
||||||
"css": "text/css",
|
|
||||||
"txt": "text/plain",
|
|
||||||
"png": "image/png",
|
|
||||||
"jpg": "image/jpeg",
|
|
||||||
"jpeg": "image/jpeg",
|
|
||||||
}
|
|
||||||
|
|
||||||
var _server: HTTPServer = null
|
|
||||||
|
|
||||||
var files: Array = []
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
_start_server()
|
|
||||||
|
|
||||||
|
|
||||||
func _start_server(port: int = 3001) -> void:
|
|
||||||
if _server:
|
|
||||||
return
|
|
||||||
|
|
||||||
_server = HTTPServer.new()
|
|
||||||
var dir := Directory.new()
|
|
||||||
if dir.open("res://server_files") == OK:
|
|
||||||
dir.list_dir_begin()
|
|
||||||
var file_name := dir.get_next()
|
|
||||||
while file_name != "":
|
|
||||||
if !dir.current_is_dir():
|
|
||||||
if file_name.get_extension() == "import":
|
|
||||||
file_name = dir.get_next()
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(file_name)
|
|
||||||
|
|
||||||
_server.endpoint(HTTPServer.Method.GET, "/%s" % file_name, funcref(self, "_serve_file"), [file_name])
|
|
||||||
|
|
||||||
file_name = dir.get_next()
|
|
||||||
|
|
||||||
_server.endpoint(HTTPServer.Method.GET, "/", funcref(self, "_serve_file"), ["index.html"])
|
|
||||||
|
|
||||||
|
|
||||||
_server.listen(port)
|
|
||||||
|
|
||||||
|
|
||||||
func _stop_server() -> void:
|
|
||||||
if _server:
|
|
||||||
_server = null
|
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
|
||||||
if _server == null:
|
|
||||||
return
|
|
||||||
|
|
||||||
_server.take_connection()
|
|
||||||
|
|
||||||
|
|
||||||
func _serve_file(request: HTTPServer.Request, response: HTTPServer.Response, binds: Array) -> void:
|
|
||||||
var file_name: String = binds[0] as String
|
|
||||||
print(file_name)
|
|
||||||
|
|
||||||
var f = File.new()
|
|
||||||
f.open("res://server_files/%s" % file_name, File.READ)
|
|
||||||
|
|
||||||
var mime = get_mime_type(file_name)
|
|
||||||
|
|
||||||
response.header("content-type", get_mime_type(file_name))
|
|
||||||
|
|
||||||
response.data(f.get_as_text())
|
|
||||||
# else:
|
|
||||||
# response.header("content-type", "text/plain")
|
|
||||||
# response.data("500 - Read Error")
|
|
||||||
|
|
||||||
|
|
||||||
func get_mime_type(file_name: String) -> String:
|
|
||||||
var ext := file_name.get_extension().to_lower()
|
|
||||||
return mime_types[ext] if ext in mime_types else "application/octet-stream"
|
|
@ -1,8 +0,0 @@
|
|||||||
[gd_scene load_steps=2 format=2]
|
|
||||||
|
|
||||||
[ext_resource path="res://Main.gd" type="Script" id=1]
|
|
||||||
|
|
||||||
[node name="Main" type="Control"]
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
script = ExtResource( 1 )
|
|
@ -4,4 +4,4 @@ Ticle is a WIP tiny static site "generator" that parses Markdown files right in
|
|||||||
|
|
||||||
This project aims to provide a nice frontend/UI to manage Ticle files and an easy way to run your site locally.
|
This project aims to provide a nice frontend/UI to manage Ticle files and an easy way to run your site locally.
|
||||||
|
|
||||||
It uses a slightly modified version of [godot-http-server](https://github.com/velopman/godot-http-server) for the server side.
|
|
||||||
|
@ -1,175 +0,0 @@
|
|||||||
class_name HTTPServer extends TCP_Server
|
|
||||||
|
|
||||||
|
|
||||||
# Public constants
|
|
||||||
|
|
||||||
const Method = preload("res://addons/http_server/method.gd")
|
|
||||||
const Request = preload("res://addons/http_server/request.gd")
|
|
||||||
const Response = preload("res://addons/http_server/response.gd")
|
|
||||||
const Status = preload("res://addons/http_server/status.gd")
|
|
||||||
|
|
||||||
|
|
||||||
# Private variables
|
|
||||||
|
|
||||||
var __endpoints: Dictionary = {
|
|
||||||
# key: [Int, String], array with 0 index representing method, 1 index representing endpoint
|
|
||||||
# value: [FuncRef, Array], index 0 = reference to function to call, index 1 = binds to pass to func
|
|
||||||
}
|
|
||||||
var __fallback: FuncRef = null
|
|
||||||
var __server: TCP_Server = null
|
|
||||||
|
|
||||||
|
|
||||||
# Public methods
|
|
||||||
|
|
||||||
func endpoint(type: int, endpoint: String, function: FuncRef, binds: Array = []) -> void:
|
|
||||||
var endpoint_hash: Array = [type, endpoint]
|
|
||||||
if endpoint_hash in __endpoints:
|
|
||||||
print(
|
|
||||||
"[ERR] Endpoint already defined type: %s, endpoint: %s" % [
|
|
||||||
Method.type_to_identifier(type),
|
|
||||||
endpoint,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
__endpoints[endpoint_hash] = [function, binds]
|
|
||||||
|
|
||||||
|
|
||||||
func fallback(function: FuncRef) -> void:
|
|
||||||
__fallback = function
|
|
||||||
|
|
||||||
|
|
||||||
func take_connection() -> StreamPeerTCP:
|
|
||||||
if !is_listening():
|
|
||||||
print(
|
|
||||||
"[ERR] Server is not listening, please initialize and listen before calling `take_connection`"
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
|
|
||||||
var connection: StreamPeerTCP = .take_connection()
|
|
||||||
|
|
||||||
if connection:
|
|
||||||
__process_connection(connection)
|
|
||||||
|
|
||||||
return connection
|
|
||||||
|
|
||||||
|
|
||||||
# Private methods
|
|
||||||
|
|
||||||
func __process_connection(connection: StreamPeerTCP) -> void:
|
|
||||||
var content: PoolByteArray = PoolByteArray([])
|
|
||||||
|
|
||||||
while true:
|
|
||||||
var bytes = connection.get_available_bytes()
|
|
||||||
if bytes == 0:
|
|
||||||
break
|
|
||||||
|
|
||||||
var data = connection.get_partial_data(bytes)
|
|
||||||
content.append_array(data[1])
|
|
||||||
|
|
||||||
if content.empty():
|
|
||||||
return
|
|
||||||
|
|
||||||
var content_string: String = content.get_string_from_utf8()
|
|
||||||
var content_parts: Array = content_string.split("\r\n")
|
|
||||||
|
|
||||||
if content_parts.empty():
|
|
||||||
connection.put_data(__response_from_status(Status.BAD_REQUEST).to_utf8())
|
|
||||||
return
|
|
||||||
|
|
||||||
var request_line = content_parts[0]
|
|
||||||
var request_line_parts = request_line.split(" ")
|
|
||||||
|
|
||||||
var method: String = request_line_parts[0]
|
|
||||||
var endpoint: String = request_line_parts[1]
|
|
||||||
|
|
||||||
var headers: Dictionary = {}
|
|
||||||
var header_index: int = content_parts.find("")
|
|
||||||
|
|
||||||
if header_index == -1:
|
|
||||||
print(
|
|
||||||
"[ERR] Error parsing request data: %s" % [String(content)]
|
|
||||||
)
|
|
||||||
connection.put_data(__response_from_status(Status.BAD_REQUEST).to_utf8())
|
|
||||||
return
|
|
||||||
|
|
||||||
for i in range(1, header_index):
|
|
||||||
var header_parts: Array = content_parts[i].split(":", true, 1)
|
|
||||||
var header = header_parts[0].strip_edges().to_lower()
|
|
||||||
var value = header_parts[1].strip_edges()
|
|
||||||
|
|
||||||
headers[header] = value
|
|
||||||
|
|
||||||
var body: String = ""
|
|
||||||
if header_index != content_parts.size() - 1:
|
|
||||||
var body_parts: Array = content_parts.slice(header_index + 1, content_parts.size())
|
|
||||||
body = PoolStringArray(body_parts).join("\r\n")
|
|
||||||
|
|
||||||
var response: Response = __process_request(method, endpoint, headers, body)
|
|
||||||
connection.put_data(response.to_utf8())
|
|
||||||
|
|
||||||
|
|
||||||
func __process_request(method: String, endpoint: String, headers: Dictionary, body: String) -> Response:
|
|
||||||
var type: int = Method.description_to_type(method)
|
|
||||||
|
|
||||||
var request: Request = Request.new(
|
|
||||||
type,
|
|
||||||
endpoint,
|
|
||||||
headers,
|
|
||||||
body
|
|
||||||
)
|
|
||||||
|
|
||||||
var endpoint_func: FuncRef = null
|
|
||||||
var endpoint_parts: PoolStringArray = endpoint.split("/", false)
|
|
||||||
var binds
|
|
||||||
|
|
||||||
# special case for if endpoint is just root
|
|
||||||
if endpoint == "/":
|
|
||||||
var endpoint_hash: Array = [type, "/"]
|
|
||||||
if __endpoints.has(endpoint_hash):
|
|
||||||
endpoint_func = __endpoints[endpoint_hash][0]
|
|
||||||
binds = __endpoints[endpoint_hash][1]
|
|
||||||
else:
|
|
||||||
while (!endpoint_func && !endpoint_parts.empty()):
|
|
||||||
var endpoint_hash: Array = [type, "/" + endpoint_parts.join("/")]
|
|
||||||
if __endpoints.has(endpoint_hash):
|
|
||||||
endpoint_func = __endpoints[endpoint_hash][0]
|
|
||||||
binds = __endpoints[endpoint_hash][1]
|
|
||||||
else:
|
|
||||||
endpoint_parts.remove(endpoint_parts.size() - 1)
|
|
||||||
|
|
||||||
|
|
||||||
if !endpoint_func:
|
|
||||||
print(
|
|
||||||
"[WRN] Recieved request for unknown endpoint, method: %s, endpoint: %s" % [method, endpoint]
|
|
||||||
)
|
|
||||||
if __fallback:
|
|
||||||
endpoint_func = __fallback
|
|
||||||
else:
|
|
||||||
return __response_from_status(Status.NOT_FOUND)
|
|
||||||
|
|
||||||
var response: Response = Response.new()
|
|
||||||
|
|
||||||
|
|
||||||
if !endpoint_func.is_valid():
|
|
||||||
print(
|
|
||||||
"[ERR] FuncRef for endpoint not valid, method: %s, endpoint: %s" % [method, endpoint]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
"[INF] Recieved request method: %s, endpoint: %s" % [method, endpoint]
|
|
||||||
)
|
|
||||||
|
|
||||||
if !binds:
|
|
||||||
endpoint_func.call_func(request, response)
|
|
||||||
else:
|
|
||||||
endpoint_func.call_func(request, response, binds)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
func __response_from_status(code: int) -> Response:
|
|
||||||
var response: Response = Response.new()
|
|
||||||
response.status(code)
|
|
||||||
|
|
||||||
return response
|
|
@ -1,61 +0,0 @@
|
|||||||
# Public Constants
|
|
||||||
|
|
||||||
enum {
|
|
||||||
GET = 0,
|
|
||||||
HEAD,
|
|
||||||
POST,
|
|
||||||
PUT,
|
|
||||||
DELETE,
|
|
||||||
CONNECT,
|
|
||||||
OPTIONS,
|
|
||||||
TRACE,
|
|
||||||
PATCH
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Private constants
|
|
||||||
|
|
||||||
const __DESCRIPTIONS: Dictionary = {
|
|
||||||
GET: "Get",
|
|
||||||
HEAD: "Head",
|
|
||||||
POST: "Post",
|
|
||||||
PUT: "Put",
|
|
||||||
DELETE: "Delete",
|
|
||||||
CONNECT: "Connect",
|
|
||||||
OPTIONS: "Options",
|
|
||||||
TRACE: "Trace",
|
|
||||||
PATCH: "Patch",
|
|
||||||
}
|
|
||||||
|
|
||||||
const __TYPES: Dictionary = {
|
|
||||||
"GET": GET,
|
|
||||||
"HEAD": HEAD,
|
|
||||||
"POST": POST,
|
|
||||||
"PUT": PUT,
|
|
||||||
"DELETE": DELETE,
|
|
||||||
"CONNECT": CONNECT,
|
|
||||||
"OPTIONS": OPTIONS,
|
|
||||||
"TRACE": TRACE,
|
|
||||||
"PATCH": PATCH,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Public methods
|
|
||||||
|
|
||||||
static func description_to_type(description: String) -> int:
|
|
||||||
return identifier_to_type(description.to_upper())
|
|
||||||
|
|
||||||
|
|
||||||
static func identifier_to_type(identifier: String) -> int:
|
|
||||||
if __TYPES.has(identifier):
|
|
||||||
return __TYPES[identifier]
|
|
||||||
|
|
||||||
return -1
|
|
||||||
|
|
||||||
|
|
||||||
static func type_to_description(type: int) -> String:
|
|
||||||
return __DESCRIPTIONS[type]
|
|
||||||
|
|
||||||
|
|
||||||
static func type_to_identifier(type: int) -> String:
|
|
||||||
return type_to_description(type).to_upper()
|
|
@ -1,7 +0,0 @@
|
|||||||
[plugin]
|
|
||||||
|
|
||||||
name="HTTPServer"
|
|
||||||
description="HTTP Server implementation for receiving HTTP requests from external sources."
|
|
||||||
author="velopman"
|
|
||||||
version="0.1"
|
|
||||||
script="plugin.gd"
|
|
@ -1,10 +0,0 @@
|
|||||||
tool
|
|
||||||
extends EditorPlugin
|
|
||||||
|
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
|
||||||
pass
|
|
@ -1,68 +0,0 @@
|
|||||||
|
|
||||||
# Public constants
|
|
||||||
|
|
||||||
const Method = preload("res://addons/http_server/method.gd")
|
|
||||||
|
|
||||||
# Private variables
|
|
||||||
|
|
||||||
var __body: String = ""
|
|
||||||
var __endpoint: String = ""
|
|
||||||
var __headers: Dictionary = {
|
|
||||||
# key: String, header name
|
|
||||||
# value: Variant, header value
|
|
||||||
}
|
|
||||||
var __json_data = null # Variant
|
|
||||||
var __type: int = Method.GET
|
|
||||||
|
|
||||||
|
|
||||||
# Lifecyle methods
|
|
||||||
|
|
||||||
func _init(type: int, endpoint: String, headers: Dictionary, body: String) -> void:
|
|
||||||
__body = body
|
|
||||||
__endpoint = endpoint
|
|
||||||
__headers = headers
|
|
||||||
__type = type
|
|
||||||
|
|
||||||
|
|
||||||
# Public methods
|
|
||||||
|
|
||||||
func body() -> String:
|
|
||||||
return __body
|
|
||||||
|
|
||||||
|
|
||||||
func endpoint() -> String:
|
|
||||||
return __endpoint
|
|
||||||
|
|
||||||
|
|
||||||
func header(name: String = "", default = null): # Variant
|
|
||||||
return __headers.get(name, default)
|
|
||||||
|
|
||||||
|
|
||||||
func headers() -> Dictionary:
|
|
||||||
return __headers
|
|
||||||
|
|
||||||
|
|
||||||
func json(): # Variant
|
|
||||||
if __json_data != null:
|
|
||||||
return __json_data
|
|
||||||
|
|
||||||
var content_type = header("content-type")
|
|
||||||
if content_type != "application/json":
|
|
||||||
print(
|
|
||||||
"[WRN] Attempting to call get_json on a request with content-type: %s" % [content_type]
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
|
|
||||||
var result = JSON.parse(__body)
|
|
||||||
if result.error:
|
|
||||||
print(
|
|
||||||
"[ERR] Error parsing request json: %s" % [result.error_string]
|
|
||||||
)
|
|
||||||
|
|
||||||
__json_data = result.result
|
|
||||||
|
|
||||||
return __json_data
|
|
||||||
|
|
||||||
|
|
||||||
func type() -> int:
|
|
||||||
return __type
|
|
@ -1,57 +0,0 @@
|
|||||||
# Public Constants
|
|
||||||
|
|
||||||
const Status = preload("res://addons/http_server/status.gd")
|
|
||||||
|
|
||||||
|
|
||||||
# Private variables
|
|
||||||
|
|
||||||
var __data = "" # variant
|
|
||||||
var __headers: Dictionary = {
|
|
||||||
# key: String, header name
|
|
||||||
# value: Variant, header value
|
|
||||||
}
|
|
||||||
var __status: int = 200
|
|
||||||
|
|
||||||
|
|
||||||
# Public methods
|
|
||||||
|
|
||||||
func data(data) -> void: # data: Variant
|
|
||||||
__data = data
|
|
||||||
|
|
||||||
|
|
||||||
func header(name: String, value) -> void: # value: Variant
|
|
||||||
__headers[name.to_lower()] = value
|
|
||||||
|
|
||||||
|
|
||||||
func json(data) -> void: # data: Variant
|
|
||||||
header("content-type", "application/json")
|
|
||||||
__data = data
|
|
||||||
|
|
||||||
|
|
||||||
func status(status: int) -> void:
|
|
||||||
__status = status
|
|
||||||
|
|
||||||
|
|
||||||
func to_utf8() -> PoolByteArray:
|
|
||||||
var content = PoolStringArray()
|
|
||||||
|
|
||||||
content.append(Status.code_to_status_line(__status))
|
|
||||||
|
|
||||||
var data = __data
|
|
||||||
if !data:
|
|
||||||
data = Status.code_to_description(__status)
|
|
||||||
|
|
||||||
if __headers.get("content-type", "") == "application/json":
|
|
||||||
data = JSON.print(data)
|
|
||||||
|
|
||||||
__headers['content-length'] = len(data)
|
|
||||||
|
|
||||||
for header in __headers:
|
|
||||||
content.append("%s: %s" % [header, String(__headers[header])])
|
|
||||||
|
|
||||||
content.append("")
|
|
||||||
|
|
||||||
if data:
|
|
||||||
content.append(data)
|
|
||||||
|
|
||||||
return content.join("\r\n").to_utf8()
|
|
@ -1,147 +0,0 @@
|
|||||||
# Public constants
|
|
||||||
|
|
||||||
enum {
|
|
||||||
CONTINUE = 100
|
|
||||||
SWITCHING_PROTOCOLS = 101
|
|
||||||
PROCESSING = 102
|
|
||||||
EARLY_HINTS = 103
|
|
||||||
OK = 200
|
|
||||||
CREATED = 201
|
|
||||||
ACCEPTED = 202
|
|
||||||
NON_AUTHORITATIVE_INFORMATION = 203
|
|
||||||
NO_CONTENT = 204
|
|
||||||
RESET_CONTENT = 205
|
|
||||||
PARTIAL_CONTENT = 206
|
|
||||||
MULTI_STATUS = 207
|
|
||||||
ALREADY_REPORTED = 208
|
|
||||||
IM_USED = 226
|
|
||||||
MULTIPLE_CHOICE = 300
|
|
||||||
MOVED_PERMANENTLY = 301
|
|
||||||
FOUND = 302
|
|
||||||
SEE_OTHER = 303
|
|
||||||
NOT_MODIFIED = 304
|
|
||||||
TEMPORARY_REDIRECT = 307
|
|
||||||
PERMANENT_REDIRECT = 308
|
|
||||||
BAD_REQUEST = 400
|
|
||||||
UNAUTHORIZED = 401
|
|
||||||
PAYMENT_REQUIRED = 402
|
|
||||||
FORBIDDEN = 403
|
|
||||||
NOT_FOUND = 404
|
|
||||||
METHOD_NOT_ALLOWED = 405
|
|
||||||
NOT_ACCEPTABLE = 406
|
|
||||||
PROXY_AUTHENTICATION_REQUIRED = 407
|
|
||||||
REQUEST_TIMEOUT = 408
|
|
||||||
CONFLICT = 409
|
|
||||||
GONE = 410
|
|
||||||
LENGTH_REQUIRED = 411
|
|
||||||
PRECONDITION_FAILED = 412
|
|
||||||
PAYLOAD_TOO_LARGE = 413
|
|
||||||
URI_TOO_LONG = 414
|
|
||||||
UNSUPPORTED_MEDIA_TYPE = 415
|
|
||||||
RANGE_NOT_SATISFIABLE = 416
|
|
||||||
EXPECTATION_FAILED = 417
|
|
||||||
IM_A_TEAPOT = 418
|
|
||||||
MISDIRECTED_REQUEST = 421
|
|
||||||
UNPROCESSABLE_ENTITY = 422
|
|
||||||
LOCKED = 423
|
|
||||||
FAILED_DEPENDENCY = 424
|
|
||||||
TOO_EARLY = 425
|
|
||||||
UPGRADE_REQUIRED = 426
|
|
||||||
PRECONDITION_REQUIRED = 428
|
|
||||||
TOO_MANY_REQUESTS = 429
|
|
||||||
REQUEST_HEADER_FIELDS_TOO_LARGE = 431
|
|
||||||
UNAVAILABLE_FOR_LEGAL_REASONS = 451
|
|
||||||
INTERNAL_SERVER_ERROR = 500
|
|
||||||
NOT_IMPLEMENTED = 501
|
|
||||||
BAD_GATEWAY = 502
|
|
||||||
SERVICE_UNAVAILABLE = 503
|
|
||||||
GATEWAY_TIMEOUT = 504
|
|
||||||
HTTP_VERSION_NOT_SUPPORTED = 505
|
|
||||||
VARIANT_ALSO_NEGOTIATES = 506
|
|
||||||
INSUFFICIENT_STORAGE = 507
|
|
||||||
LOOP_DETECTED = 508
|
|
||||||
NOT_EXTENDED = 510
|
|
||||||
NETWORK_AUTHENTICATION_REQUIRED = 511
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Private constants
|
|
||||||
|
|
||||||
const __DESCRIPTIONS: Dictionary = {
|
|
||||||
CONTINUE: "Continue",
|
|
||||||
SWITCHING_PROTOCOLS: "Switching Protocols",
|
|
||||||
PROCESSING: "Processing",
|
|
||||||
EARLY_HINTS: "Early Hints",
|
|
||||||
OK: "Ok",
|
|
||||||
CREATED: "Created",
|
|
||||||
ACCEPTED: "Accepted",
|
|
||||||
NON_AUTHORITATIVE_INFORMATION: "Non-Authoritative Information",
|
|
||||||
NO_CONTENT: "No Content",
|
|
||||||
RESET_CONTENT: "Reset Content",
|
|
||||||
PARTIAL_CONTENT: "Partial Content",
|
|
||||||
MULTI_STATUS: "Multi-Status",
|
|
||||||
ALREADY_REPORTED: "Already Reported",
|
|
||||||
IM_USED: "IM Used",
|
|
||||||
MULTIPLE_CHOICE: "Multiple Choice",
|
|
||||||
MOVED_PERMANENTLY: "Moved Permanently",
|
|
||||||
FOUND: "Found",
|
|
||||||
SEE_OTHER: "See Other",
|
|
||||||
NOT_MODIFIED: "Not Modified",
|
|
||||||
TEMPORARY_REDIRECT: "Temporary Redirect",
|
|
||||||
PERMANENT_REDIRECT: "Permanent Redirect",
|
|
||||||
BAD_REQUEST: "Bad Request",
|
|
||||||
UNAUTHORIZED: "Unauthorized",
|
|
||||||
PAYMENT_REQUIRED: "Payment Required",
|
|
||||||
FORBIDDEN: "Forbidden",
|
|
||||||
NOT_FOUND: "Not Found",
|
|
||||||
METHOD_NOT_ALLOWED: "Method Not Allowed",
|
|
||||||
NOT_ACCEPTABLE: "Not Acceptable",
|
|
||||||
PROXY_AUTHENTICATION_REQUIRED: "Proxy Authentication Requested",
|
|
||||||
REQUEST_TIMEOUT: "Request Timeout",
|
|
||||||
CONFLICT: "Conflict",
|
|
||||||
GONE: "Gone",
|
|
||||||
LENGTH_REQUIRED: "Length Required",
|
|
||||||
PRECONDITION_FAILED: "Precondition Failed",
|
|
||||||
PAYLOAD_TOO_LARGE: "Payload Too Large",
|
|
||||||
URI_TOO_LONG: "URI Too long",
|
|
||||||
UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type",
|
|
||||||
RANGE_NOT_SATISFIABLE: "Range Not Satisfiable",
|
|
||||||
EXPECTATION_FAILED: "Expectation Failed",
|
|
||||||
IM_A_TEAPOT: "I'm A Teapot",
|
|
||||||
MISDIRECTED_REQUEST: "Misdirected Request",
|
|
||||||
UNPROCESSABLE_ENTITY: "Unprocessable Entity",
|
|
||||||
LOCKED: "Locked",
|
|
||||||
FAILED_DEPENDENCY: "Failed Dependency",
|
|
||||||
TOO_EARLY: "Too Early",
|
|
||||||
UPGRADE_REQUIRED: "Upgrade Required",
|
|
||||||
PRECONDITION_REQUIRED: "Precondition Required",
|
|
||||||
TOO_MANY_REQUESTS: "Too Many Requests",
|
|
||||||
REQUEST_HEADER_FIELDS_TOO_LARGE: "Request Header Fields Too Large",
|
|
||||||
UNAVAILABLE_FOR_LEGAL_REASONS: "Unavailable For Legal Reasons",
|
|
||||||
INTERNAL_SERVER_ERROR: "Internal Server Error",
|
|
||||||
NOT_IMPLEMENTED: "Not Implemented",
|
|
||||||
BAD_GATEWAY: "Bad Gateway",
|
|
||||||
SERVICE_UNAVAILABLE: "Service Unavailable",
|
|
||||||
GATEWAY_TIMEOUT: "Gateway Timeout",
|
|
||||||
HTTP_VERSION_NOT_SUPPORTED: "HTTP Version Not Supported",
|
|
||||||
VARIANT_ALSO_NEGOTIATES: "Variant Also Negotiates",
|
|
||||||
INSUFFICIENT_STORAGE: "Insufficient Storage",
|
|
||||||
LOOP_DETECTED: "Loop detected",
|
|
||||||
NOT_EXTENDED: "Not Extended",
|
|
||||||
NETWORK_AUTHENTICATION_REQUIRED: "Network Authentication Required",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Public methods
|
|
||||||
|
|
||||||
static func code_to_description(code: int) -> String:
|
|
||||||
return __DESCRIPTIONS[code]
|
|
||||||
|
|
||||||
|
|
||||||
static func code_to_identifier(code: int) -> String:
|
|
||||||
return code_to_description(code).to_upper().replace(" ", "_").replace("'", "")
|
|
||||||
|
|
||||||
|
|
||||||
static func code_to_status_line(code: int) -> String:
|
|
||||||
return "HTTP/1.1 %d %s" % [code, code_to_identifier(code)]
|
|
||||||
|
|
@ -8,26 +8,11 @@
|
|||||||
|
|
||||||
config_version=4
|
config_version=4
|
||||||
|
|
||||||
_global_script_classes=[ {
|
|
||||||
"base": "TCP_Server",
|
|
||||||
"class": "HTTPServer",
|
|
||||||
"language": "GDScript",
|
|
||||||
"path": "res://addons/http_server/http_server.gd"
|
|
||||||
} ]
|
|
||||||
_global_script_class_icons={
|
|
||||||
"HTTPServer": ""
|
|
||||||
}
|
|
||||||
|
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="Ticle Frontend"
|
config/name="Ticle Frontend"
|
||||||
run/main_scene="res://Main.tscn"
|
|
||||||
config/icon="res://icon.png"
|
config/icon="res://icon.png"
|
||||||
|
|
||||||
[editor_plugins]
|
|
||||||
|
|
||||||
enabled=PoolStringArray( "res://addons/http_server/plugin.cfg" )
|
|
||||||
|
|
||||||
[gui]
|
[gui]
|
||||||
|
|
||||||
common/drop_mouse_on_gui_input_disabled=true
|
common/drop_mouse_on_gui_input_disabled=true
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 35 KiB |
@ -1,35 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="StreamTexture"
|
|
||||||
path="res://.import/dogpepsi.jpg-600a9f60613039ee9dabc11447d355f1.stex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://server_files/dogpepsi.jpg"
|
|
||||||
dest_files=[ "res://.import/dogpepsi.jpg-600a9f60613039ee9dabc11447d355f1.stex" ]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=0
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/hdr_mode=0
|
|
||||||
compress/bptc_ldr=0
|
|
||||||
compress/normal_map=0
|
|
||||||
flags/repeat=0
|
|
||||||
flags/filter=true
|
|
||||||
flags/mipmaps=false
|
|
||||||
flags/anisotropic=false
|
|
||||||
flags/srgb=2
|
|
||||||
process/fix_alpha_border=true
|
|
||||||
process/premult_alpha=false
|
|
||||||
process/HDR_as_SRGB=false
|
|
||||||
process/invert_color=false
|
|
||||||
process/normal_map_invert_y=false
|
|
||||||
stream=false
|
|
||||||
size_limit=0
|
|
||||||
detect_3d=true
|
|
||||||
svg/scale=1.0
|
|
@ -1,3 +0,0 @@
|
|||||||
first.md 2022-01-06 first article
|
|
||||||
second.md 2021-05-03 second article
|
|
||||||
second.md
|
|
@ -1,47 +0,0 @@
|
|||||||
# Importance of Second Opinions
|
|
||||||
|
|
||||||
|
|
||||||
A Mayo Clinic study found that
|
|
||||||
Only 1/10 cases of patients seeking second opinions
|
|
||||||
received confirmation the first diagnosis was complete and correct
|
|
||||||
2/10 cases received a "distinctly different" diagnosis
|
|
||||||
|
|
||||||
Yale Medecine recommends to seek a second opinion
|
|
||||||
when the diagnosis is cancer
|
|
||||||
or
|
|
||||||
when surgery is recommended
|
|
||||||
|
|
||||||
Please tell your loved ones to seek 2nd opinions!
|
|
||||||
|
|
||||||
-----
|
|
||||||
|
|
||||||
First Opinions are often bad:
|
|
||||||
|
|
||||||
- https://newsnetwork.mayoclinic.org/discussion/mayo-clinic-researchers-demonstrate-value-of-second-opinions/
|
|
||||||
- https://newsnetwork.mayoclinic.org/discussion/mayo-clinic-researchers-demonstrate-value-of-second-opinions/
|
|
||||||
|
|
||||||
|
|
||||||
## Key points:
|
|
||||||
|
|
||||||
> The Mayo Clinic study found that as many as
|
|
||||||
> - 9 out of 10 patients seeking a second opinion go home with a new or refined diagnosis.
|
|
||||||
> - 2 out of ten received a “distinctly different” diagnosis.
|
|
||||||
>- only 1 out of 10 referred patients receive confirmation that the original
|
|
||||||
> diagnosis was complete and correct.
|
|
||||||
> This is _out of patients seeking a 2nd opinion_, not patients in general
|
|
||||||
|
|
||||||
Paper referenced by articles found at: https://onlinelibrary.wiley.com/doi/abs/10.1111/jep.12747
|
|
||||||
|
|
||||||
-----
|
|
||||||
|
|
||||||
When to Seek 2nd Opinions according to Yale
|
|
||||||
|
|
||||||
- https://www.yalemedicine.org/news/second-opinions
|
|
||||||
|
|
||||||
Key points:
|
|
||||||
|
|
||||||
> Seek 2nd opinion:
|
|
||||||
> - When the diagnosis is cancer
|
|
||||||
> - **When surgery is recommended**
|
|
||||||
|
|
||||||
-----
|
|
@ -1,405 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Parcel Sandbox</title>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<style></style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<template id="my-link">
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
--background-regular: hsla(196, 61%, 58%, 0.75);
|
|
||||||
--background-active: red;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #18272f;
|
|
||||||
font-weight: 700;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host span {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host::before {
|
|
||||||
content: "";
|
|
||||||
background-color: var(--background-regular);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 3px;
|
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
|
||||||
z-index: -1;
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host(:hover)::before {
|
|
||||||
bottom: 0;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([active])::before {
|
|
||||||
background-color: var(--background-active);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<span><slot /></span>
|
|
||||||
</template>
|
|
||||||
<template id="my-menu">
|
|
||||||
<style>
|
|
||||||
:host ul,
|
|
||||||
:host li {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
:host nav {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<nav>
|
|
||||||
<slot />
|
|
||||||
</nav>
|
|
||||||
</template>
|
|
||||||
<my-menu id="menu">
|
|
||||||
<my-link main href="d">Home</my-link>
|
|
||||||
<h2>Articles</h2>
|
|
||||||
</my-menu>
|
|
||||||
<div id="App"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
//@ts-check
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
*
|
|
||||||
* UTILITIES
|
|
||||||
*
|
|
||||||
* A few common methods to use in the project
|
|
||||||
*
|
|
||||||
*********************************************************************/
|
|
||||||
|
|
||||||
const Signal = () => {
|
|
||||||
const listeners = new Set();
|
|
||||||
|
|
||||||
return {
|
|
||||||
remove: listeners.delete.bind(listeners),
|
|
||||||
add(/** @type {(arg:any)=>void} */ listener) {
|
|
||||||
listeners.add(listener);
|
|
||||||
return listeners.delete.bind(listeners, listener);
|
|
||||||
},
|
|
||||||
emit(/** @type {any} */ data) {
|
|
||||||
listeners.forEach((l) => l(data));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getText = (/** @type {string} */ file) =>
|
|
||||||
fetch(`./${file}`)
|
|
||||||
.then((response) => response.text())
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(`could not find file "${file}"`);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
const parseMarkdown = (/** @type {string} */ text) =>
|
|
||||||
text
|
|
||||||
// lists
|
|
||||||
.replace(
|
|
||||||
/^\s*\n((?:\*\s.+\s*\n)+)([^\*])/gm,
|
|
||||||
(_, bullets, next) =>
|
|
||||||
`<ul>${bullets.replace(
|
|
||||||
/^\*\s(.+)/gm,
|
|
||||||
"<li>$1</li>"
|
|
||||||
)}\n</ul>\n\n${next}`
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/^\s*\n((?:\d\..+\s*\n)+)([^\*])/gm,
|
|
||||||
(_, bullets, next) =>
|
|
||||||
`<ol>${bullets.replace(
|
|
||||||
/^\d\.\s(.+)/gm,
|
|
||||||
"<li>$1</li>"
|
|
||||||
)}\n</ol>\n\n${next}`
|
|
||||||
)
|
|
||||||
// blockquotes
|
|
||||||
.replace(/^\>(.+)/gm, "<blockquote>$1</blockquote>")
|
|
||||||
// headers
|
|
||||||
.replace(/(#+)(.+)/g, (_, { length: l }, t) => `<h${l}>${t}</h${l}>`)
|
|
||||||
.replace(/^(.+)\n\=+/gm, "<h1>$1</h1>")
|
|
||||||
.replace(/^(.+)\n\-+/gm, "<h2>$1</h2>")
|
|
||||||
//images
|
|
||||||
.replace(/\!\[([^\]]+)\]\(([^\)]+)\)/g, '<img src="$2" alt="$1" />')
|
|
||||||
//links
|
|
||||||
.replace(
|
|
||||||
/[\[]{1}([^\]]+)[\]]{1}[\(]{1}([^\)\"]+)(\"(.+)\")?[\)]{1}/g,
|
|
||||||
'<a href="$2" title="$4">$1</a>'
|
|
||||||
)
|
|
||||||
//font styles
|
|
||||||
.replace(/[\*\_]{2}([^\*\_]+)[\*\_]{2}/g, "<strong>$1</strong>")
|
|
||||||
.replace(/[\*\_]{1}([^\*\_]+)[\*\_]{1}/g, "<em>$1</em>")
|
|
||||||
.replace(/[\~]{2}([^\~]+)[\~]{2}/g, "<del>$1</del>")
|
|
||||||
//pre
|
|
||||||
.replace(/^\s*\n\`\`\`(([^\s]+))?/gm, '<pre class="$2">')
|
|
||||||
.replace(/^\`\`\`\s*\n/gm, "</pre>\n\n")
|
|
||||||
//code
|
|
||||||
.replace(/[\`]{1}([^\`]+)[\`]{1}/g, "<code>$1</code>")
|
|
||||||
//p
|
|
||||||
.replace(/^\s*(\n)?(.+)/gm, (m) => {
|
|
||||||
return /\<(\/)?(h\d|ul|ol|li|blockquote|pre|img)/.test(m)
|
|
||||||
? m
|
|
||||||
: "<p>" + m + "</p>";
|
|
||||||
})
|
|
||||||
//strip p from pre
|
|
||||||
.replace(/(\<pre.+\>)\s*\n\<p\>(.+)\<\/p\>/gm, "$1$2")
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
const getMarkdown = (/** @type {string} */ file) =>
|
|
||||||
getText(file).then(parseMarkdown);
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} tag
|
|
||||||
* @param {Record<string, any>} props
|
|
||||||
* @param {string|Node[]} children
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const el = (tag = "div", props = {}, children = []) => {
|
|
||||||
const node = document.createElement(tag);
|
|
||||||
Object.keys(props).forEach((key) => {
|
|
||||||
node.setAttribute(key, props[key]);
|
|
||||||
});
|
|
||||||
if (typeof children == "string") {
|
|
||||||
children = [document.createTextNode(children)];
|
|
||||||
}
|
|
||||||
children.forEach((child) => node.appendChild(child));
|
|
||||||
return node;
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeTitelize = (alwaysLowCaps = [], alwaysUpperCaps = []) => {
|
|
||||||
const specials = [...alwaysLowCaps, ...alwaysUpperCaps].reduce(
|
|
||||||
(result, word) =>
|
|
||||||
result.set(new RegExp("\\b" + word + "\\b", "gi"), word),
|
|
||||||
/** @type {Map<RegExp, string>}*/ (new Map())
|
|
||||||
);
|
|
||||||
const titelize = (/** @type {string} */ text) => {
|
|
||||||
text = text
|
|
||||||
.replace(/_-\//g, " ")
|
|
||||||
.replace(/\.\w+$/, "")
|
|
||||||
.replace(/\s+/, " ")
|
|
||||||
.split(" ")
|
|
||||||
.map((word) =>
|
|
||||||
word.length > 1
|
|
||||||
? word[0].toUpperCase() + word.slice(1).toLowerCase()
|
|
||||||
: word
|
|
||||||
)
|
|
||||||
.join(" ");
|
|
||||||
for (const [key, value] of specials) {
|
|
||||||
text = text.replace(key, value);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
return titelize;
|
|
||||||
};
|
|
||||||
|
|
||||||
const titelize = makeTitelize(["the", "a"], ["TV", "ID", "AI"]);
|
|
||||||
|
|
||||||
const Router = (() => {
|
|
||||||
const onRouteChange = Signal();
|
|
||||||
let route = "";
|
|
||||||
|
|
||||||
const set = (/** @type {string} */ newRoute) => {
|
|
||||||
if (newRoute === route) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
window.location.hash = newRoute;
|
|
||||||
route = newRoute;
|
|
||||||
onRouteChange.emit(route);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const get = () => window.location.hash.slice(1).replace(/\//gi, "/");
|
|
||||||
|
|
||||||
const is = (href) => href === get();
|
|
||||||
|
|
||||||
window.addEventListener("popstate", () => set(get()));
|
|
||||||
|
|
||||||
return { set, get, is, onRouteChange };
|
|
||||||
})();
|
|
||||||
|
|
||||||
const getTemplateClone = (/** @type {string} */ id) => {
|
|
||||||
const templateModel = /** @type {HTMLTemplateElement} */ (
|
|
||||||
document.getElementById(id)
|
|
||||||
);
|
|
||||||
const template = /** @type {HTMLElement} */ (
|
|
||||||
templateModel.content.cloneNode(true)
|
|
||||||
);
|
|
||||||
return template;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* WEB COMPONENTS
|
|
||||||
*
|
|
||||||
* Sources:
|
|
||||||
* https://web.dev/custom-elements-best-practices/
|
|
||||||
* https://googlechromelabs.github.io/howto-components/
|
|
||||||
*
|
|
||||||
* A set of neat components to use in the page
|
|
||||||
*
|
|
||||||
*********************************************************************/
|
|
||||||
|
|
||||||
class CustomElement extends HTMLElement {
|
|
||||||
/** @type {ShadowRoot} */
|
|
||||||
shadow = this.attachShadow({ mode: "closed" });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A user may set a property on an instance of an element, before its prototype has been connected to this class.
|
|
||||||
* Will check for any instance properties and run them through the proper class setters.
|
|
||||||
* @param {string} prop
|
|
||||||
*/
|
|
||||||
_syncProperty(prop) {
|
|
||||||
if (this.hasOwnProperty(prop)) {
|
|
||||||
let value = this[prop];
|
|
||||||
delete this[prop];
|
|
||||||
this[prop] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyLink extends CustomElement {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.shadow.append(getTemplateClone("my-link"));
|
|
||||||
this.shadow.addEventListener("click", this._onClick.bind(this));
|
|
||||||
Router.onRouteChange.add(this.updateActive.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
static get observedAttributes() {
|
|
||||||
return ["href", "active", "main"];
|
|
||||||
}
|
|
||||||
|
|
||||||
_onClick() {
|
|
||||||
if (this.href) {
|
|
||||||
Router.set(this.href);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attributeChangedCallback(property, oldValue, newValue) {
|
|
||||||
if (oldValue === newValue) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this[property] = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateActive() {
|
|
||||||
if (Router.is(this.href)) {
|
|
||||||
this.setAttribute("active", "");
|
|
||||||
} else {
|
|
||||||
this.removeAttribute("active");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set href(/** @type {string}*/ value) {
|
|
||||||
this.setAttribute("href", value);
|
|
||||||
this.updateActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
get href() {
|
|
||||||
return this.getAttribute("href");
|
|
||||||
}
|
|
||||||
|
|
||||||
set main(/** @type {boolean}*/ value) {
|
|
||||||
if (value) {
|
|
||||||
this.setAttribute("main", "");
|
|
||||||
} else {
|
|
||||||
this.removeAttribute("main");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get main() {
|
|
||||||
return this.hasAttribute("main");
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
["active", "main"].forEach((prop) => this._syncProperty(prop));
|
|
||||||
this.updateActive();
|
|
||||||
if (this.getAttribute("main")) {
|
|
||||||
console.log("sdfsdff");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("my-link", MyLink);
|
|
||||||
|
|
||||||
class MyMenu extends CustomElement {
|
|
||||||
_handled = new Set();
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.shadow.append(getTemplateClone("my-menu"));
|
|
||||||
const slot = this.shadow.querySelector("slot");
|
|
||||||
slot.addEventListener("slotchange", (event) => {
|
|
||||||
for (const child of slot.assignedElements()) {
|
|
||||||
if (this._handled.has(child) || !(child instanceof MyLink)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this._handled.add(child);
|
|
||||||
// TODO: pre-fetch
|
|
||||||
//console.log("new child: ", child);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
customElements.define("my-menu", MyMenu);
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* MARKDOWN PARSING
|
|
||||||
*********************************************************************/
|
|
||||||
|
|
||||||
const load = (/** @type {string} */ file) =>
|
|
||||||
getMarkdown(file).then((md) => {
|
|
||||||
app.innerHTML = md;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* BOOSTRAPPING
|
|
||||||
*********************************************************************/
|
|
||||||
|
|
||||||
getText("files.txt").then((lines) => {
|
|
||||||
lines
|
|
||||||
.split(`\n`)
|
|
||||||
.map((line) => {
|
|
||||||
const [file, maybeDate, ...rest] = line.split(/\s/);
|
|
||||||
const href = file.trim();
|
|
||||||
let date = maybeDate ? new Date(maybeDate) : new Date();
|
|
||||||
if (isNaN(date.getTime())) {
|
|
||||||
date = new Date();
|
|
||||||
rest.unshift(maybeDate);
|
|
||||||
}
|
|
||||||
const textContent = rest.length
|
|
||||||
? rest.join(" ").trim()
|
|
||||||
: titelize(file);
|
|
||||||
return { href, date, textContent };
|
|
||||||
})
|
|
||||||
.sort(({ date: a }, { date: b }) => a.getTime() - b.getTime())
|
|
||||||
.forEach(({ href, date, textContent }) => {
|
|
||||||
const link = /** @type {MyLink} */ el(
|
|
||||||
"my-link",
|
|
||||||
{ href },
|
|
||||||
textContent
|
|
||||||
);
|
|
||||||
document.getElementById("menu").appendChild(link);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const app = document.getElementById("App");
|
|
||||||
|
|
||||||
const BLOCKQUOTE = Symbol("blockquote");
|
|
||||||
const PARAGRAPH = Symbol("paragraph");
|
|
||||||
const LIST = Symbol("list");
|
|
||||||
|
|
||||||
Router.onRouteChange.add((route) => load(route));
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1 +0,0 @@
|
|||||||
# Title
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.0 MiB |
@ -1,35 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="StreamTexture"
|
|
||||||
path="res://.import/picture.png-f7b364942b19109d5768d9400167481e.stex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://server_files/picture.png"
|
|
||||||
dest_files=[ "res://.import/picture.png-f7b364942b19109d5768d9400167481e.stex" ]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
compress/mode=0
|
|
||||||
compress/lossy_quality=0.7
|
|
||||||
compress/hdr_mode=0
|
|
||||||
compress/bptc_ldr=0
|
|
||||||
compress/normal_map=0
|
|
||||||
flags/repeat=0
|
|
||||||
flags/filter=true
|
|
||||||
flags/mipmaps=false
|
|
||||||
flags/anisotropic=false
|
|
||||||
flags/srgb=2
|
|
||||||
process/fix_alpha_border=true
|
|
||||||
process/premult_alpha=false
|
|
||||||
process/HDR_as_SRGB=false
|
|
||||||
process/invert_color=false
|
|
||||||
process/normal_map_invert_y=false
|
|
||||||
stream=false
|
|
||||||
size_limit=0
|
|
||||||
detect_3d=true
|
|
||||||
svg/scale=1.0
|
|
@ -1,20 +0,0 @@
|
|||||||
# Testing markdown
|
|
||||||
|
|
||||||
This is a test of **markdown** parsing. Again. Because why not?
|
|
||||||
|
|
||||||
Why:
|
|
||||||
|
|
||||||
1. Item1
|
|
||||||
2. Item2
|
|
||||||
|
|
||||||
> Blockquote
|
|
||||||
|
|
||||||
Image:
|
|
||||||
|
|
||||||
![alt text](picture.png)
|
|
||||||
|
|
||||||
```
|
|
||||||
code_block!
|
|
||||||
```
|
|
||||||
|
|
||||||
`code not block`
|
|
Loading…
Reference in New Issue
Block a user