feature: add a map

This commit is contained in:
Andy Burke 2026-01-15 20:53:57 -08:00
parent 19afb7f9fa
commit 32ed2dfd33
23 changed files with 29839 additions and 130 deletions

View file

@ -11,11 +11,15 @@
"test": "DENO_ENV=test FSDB_ROOT=$PWD/tests/data/$(date --iso-8601=seconds) SERVERUS_ROOT=$PWD/public SERVERUS_PUT_PATHS_ALLOWED=./files SERVERUS_DELETE_PATHS_ALLOWED=./files deno test --allow-env --allow-read --allow-write --allow-net --allow-import --trace-leaks --fail-fast tests/" "test": "DENO_ENV=test FSDB_ROOT=$PWD/tests/data/$(date --iso-8601=seconds) SERVERUS_ROOT=$PWD/public SERVERUS_PUT_PATHS_ALLOWED=./files SERVERUS_DELETE_PATHS_ALLOWED=./files deno test --allow-env --allow-read --allow-write --allow-net --allow-import --trace-leaks --fail-fast tests/"
}, },
"test": { "test": {
"exclude": ["tests/data/"] "exclude": [
"tests/data/"
]
}, },
"compilerOptions": {}, "compilerOptions": {},
"fmt": { "fmt": {
"include": ["**/*.ts"], "include": [
"**/*.ts"
],
"options": { "options": {
"useTabs": true, "useTabs": true,
"lineWidth": 180, "lineWidth": 180,
@ -25,22 +29,28 @@
} }
}, },
"lint": { "lint": {
"include": ["**/*.ts"], "include": [
"**/*.ts"
],
"rules": { "rules": {
"tags": ["recommended"], "tags": [
"exclude": ["no-explicit-any"] "recommended"
],
"exclude": [
"no-explicit-any"
]
} }
}, },
"imports": { "imports": {
"@andyburke/fsdb": "jsr:@andyburke/fsdb@^1.2.4", "@andyburke/fsdb": "jsr:@andyburke/fsdb@^1.2.4",
"@andyburke/lurid": "jsr:@andyburke/lurid@^0.2.0", "@andyburke/lurid": "jsr:@andyburke/lurid@^0.2.0",
"@andyburke/serverus": "jsr:@andyburke/serverus@^0.13.0", "@andyburke/serverus": "jsr:@andyburke/serverus@^0.15.0",
"@da/bcrypt": "jsr:@da/bcrypt@^1.0.1", "@da/bcrypt": "jsr:@da/bcrypt@^1.0.1",
"@std/assert": "jsr:@std/assert@^1.0.15", "@std/assert": "jsr:@std/assert@^1.0.16",
"@std/encoding": "jsr:@std/encoding@^1.0.10", "@std/encoding": "jsr:@std/encoding@^1.0.10",
"@std/fs": "jsr:@std/fs@^1.0.19", "@std/fs": "jsr:@std/fs@^1.0.21",
"@std/http": "jsr:@std/http@^1.0.21", "@std/http": "jsr:@std/http@^1.0.23",
"@std/media-types": "jsr:@std/media-types@^1.1.0", "@std/media-types": "jsr:@std/media-types@^1.1.0",
"@std/path": "jsr:@std/path@^1.1.2" "@std/path": "jsr:@std/path@^1.1.4"
} }
} }

83
deno.lock generated
View file

@ -3,31 +3,30 @@
"specifiers": { "specifiers": {
"jsr:@andyburke/fsdb@^1.2.4": "1.2.4", "jsr:@andyburke/fsdb@^1.2.4": "1.2.4",
"jsr:@andyburke/lurid@0.2": "0.2.0", "jsr:@andyburke/lurid@0.2": "0.2.0",
"jsr:@andyburke/serverus@0.13": "0.13.0", "jsr:@andyburke/serverus@0.15": "0.15.0",
"jsr:@da/bcrypt@*": "1.0.1", "jsr:@da/bcrypt@*": "1.0.1",
"jsr:@da/bcrypt@^1.0.1": "1.0.1", "jsr:@da/bcrypt@^1.0.1": "1.0.1",
"jsr:@std/assert@^1.0.15": "1.0.15", "jsr:@std/assert@^1.0.16": "1.0.16",
"jsr:@std/cli@^1.0.19": "1.0.23", "jsr:@std/cli@^1.0.19": "1.0.25",
"jsr:@std/cli@^1.0.20": "1.0.23", "jsr:@std/cli@^1.0.20": "1.0.25",
"jsr:@std/cli@^1.0.21": "1.0.23", "jsr:@std/cli@^1.0.21": "1.0.25",
"jsr:@std/cli@^1.0.23": "1.0.23", "jsr:@std/cli@^1.0.25": "1.0.25",
"jsr:@std/encoding@^1.0.10": "1.0.10", "jsr:@std/encoding@^1.0.10": "1.0.10",
"jsr:@std/fmt@^1.0.6": "1.0.8", "jsr:@std/fmt@^1.0.6": "1.0.8",
"jsr:@std/fmt@^1.0.8": "1.0.8", "jsr:@std/fmt@^1.0.8": "1.0.8",
"jsr:@std/fs@^1.0.18": "1.0.19", "jsr:@std/fs@^1.0.18": "1.0.21",
"jsr:@std/fs@^1.0.19": "1.0.19", "jsr:@std/fs@^1.0.19": "1.0.21",
"jsr:@std/fs@^1.0.21": "1.0.21",
"jsr:@std/html@^1.0.5": "1.0.5", "jsr:@std/html@^1.0.5": "1.0.5",
"jsr:@std/http@^1.0.20": "1.0.21", "jsr:@std/http@^1.0.20": "1.0.23",
"jsr:@std/http@^1.0.21": "1.0.21", "jsr:@std/http@^1.0.23": "1.0.23",
"jsr:@std/internal@^1.0.10": "1.0.12",
"jsr:@std/internal@^1.0.12": "1.0.12", "jsr:@std/internal@^1.0.12": "1.0.12",
"jsr:@std/internal@^1.0.9": "1.0.12",
"jsr:@std/media-types@^1.1.0": "1.1.0", "jsr:@std/media-types@^1.1.0": "1.1.0",
"jsr:@std/net@^1.0.6": "1.0.6", "jsr:@std/net@^1.0.6": "1.0.6",
"jsr:@std/path@^1.1.0": "1.1.2", "jsr:@std/path@^1.1.0": "1.1.4",
"jsr:@std/path@^1.1.1": "1.1.2", "jsr:@std/path@^1.1.1": "1.1.4",
"jsr:@std/path@^1.1.2": "1.1.2", "jsr:@std/path@^1.1.4": "1.1.4",
"jsr:@std/streams@^1.0.13": "1.0.13", "jsr:@std/streams@^1.0.16": "1.0.16",
"npm:@types/node@*": "22.15.15" "npm:@types/node@*": "22.15.15"
}, },
"jsr": { "jsr": {
@ -45,8 +44,8 @@
"jsr:@std/cli@^1.0.19" "jsr:@std/cli@^1.0.19"
] ]
}, },
"@andyburke/serverus@0.13.0": { "@andyburke/serverus@0.15.0": {
"integrity": "73f451e1b68cd9be3938333b06290bfeab275361453559f40dfeab19dc4ad6d7", "integrity": "bc533ede60f6022bfcc4c380597f34b17d73172af566496160319cb5275977bd",
"dependencies": [ "dependencies": [
"jsr:@std/cli@^1.0.21", "jsr:@std/cli@^1.0.21",
"jsr:@std/fmt@^1.0.6", "jsr:@std/fmt@^1.0.6",
@ -59,14 +58,14 @@
"@da/bcrypt@1.0.1": { "@da/bcrypt@1.0.1": {
"integrity": "d2172d3acbcff52e0465557a1a48b1ff1c92df08c90712dae5372255a8c45eb3" "integrity": "d2172d3acbcff52e0465557a1a48b1ff1c92df08c90712dae5372255a8c45eb3"
}, },
"@std/assert@1.0.15": { "@std/assert@1.0.16": {
"integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b", "integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532",
"dependencies": [ "dependencies": [
"jsr:@std/internal@^1.0.12" "jsr:@std/internal"
] ]
}, },
"@std/cli@1.0.23": { "@std/cli@1.0.25": {
"integrity": "bf95b7a9425ba2af1ae5a6359daf58c508f2decf711a76ed2993cd352498ccca" "integrity": "1f85051b370c97a7a9dfc6ba626e7ed57a91bea8c081597276d1e78d929d8c91"
}, },
"@std/encoding@1.0.10": { "@std/encoding@1.0.10": {
"integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
@ -74,27 +73,27 @@
"@std/fmt@1.0.8": { "@std/fmt@1.0.8": {
"integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7"
}, },
"@std/fs@1.0.19": { "@std/fs@1.0.21": {
"integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06", "integrity": "d720fe1056d78d43065a4d6e0eeb2b19f34adb8a0bc7caf3a4dbf1d4178252cd",
"dependencies": [ "dependencies": [
"jsr:@std/internal@^1.0.9", "jsr:@std/internal",
"jsr:@std/path@^1.1.1" "jsr:@std/path@^1.1.4"
] ]
}, },
"@std/html@1.0.5": { "@std/html@1.0.5": {
"integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e" "integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e"
}, },
"@std/http@1.0.21": { "@std/http@1.0.23": {
"integrity": "abb5c747651ee6e3ea6139858fd9b1810d2c97f53a5e6722f3b6d27a6d263edc", "integrity": "6634e9e034c589bf35101c1b5ee5bbf052a5987abca20f903e58bdba85c80dee",
"dependencies": [ "dependencies": [
"jsr:@std/cli@^1.0.23", "jsr:@std/cli@^1.0.25",
"jsr:@std/encoding", "jsr:@std/encoding",
"jsr:@std/fmt@^1.0.8", "jsr:@std/fmt@^1.0.8",
"jsr:@std/fs@^1.0.19", "jsr:@std/fs@^1.0.21",
"jsr:@std/html", "jsr:@std/html",
"jsr:@std/media-types", "jsr:@std/media-types",
"jsr:@std/net", "jsr:@std/net",
"jsr:@std/path@^1.1.2", "jsr:@std/path@^1.1.4",
"jsr:@std/streams" "jsr:@std/streams"
] ]
}, },
@ -107,14 +106,14 @@
"@std/net@1.0.6": { "@std/net@1.0.6": {
"integrity": "110735f93e95bb9feb95790a8b1d1bf69ec0dc74f3f97a00a76ea5efea25500c" "integrity": "110735f93e95bb9feb95790a8b1d1bf69ec0dc74f3f97a00a76ea5efea25500c"
}, },
"@std/path@1.1.2": { "@std/path@1.1.4": {
"integrity": "c0b13b97dfe06546d5e16bf3966b1cadf92e1cc83e56ba5476ad8b498d9e3038", "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5",
"dependencies": [ "dependencies": [
"jsr:@std/internal@^1.0.10" "jsr:@std/internal"
] ]
}, },
"@std/streams@1.0.13": { "@std/streams@1.0.16": {
"integrity": "772d208cd0d3e5dac7c1d9e6cdb25842846d136eea4a41a62e44ed4ab0c8dd9e" "integrity": "85030627befb1767c60d4f65cb30fa2f94af1d6ee6e5b2515b76157a542e89c4"
} }
}, },
"npm": { "npm": {
@ -135,14 +134,14 @@
"dependencies": [ "dependencies": [
"jsr:@andyburke/fsdb@^1.2.4", "jsr:@andyburke/fsdb@^1.2.4",
"jsr:@andyburke/lurid@0.2", "jsr:@andyburke/lurid@0.2",
"jsr:@andyburke/serverus@0.13", "jsr:@andyburke/serverus@0.15",
"jsr:@da/bcrypt@^1.0.1", "jsr:@da/bcrypt@^1.0.1",
"jsr:@std/assert@^1.0.15", "jsr:@std/assert@^1.0.16",
"jsr:@std/encoding@^1.0.10", "jsr:@std/encoding@^1.0.10",
"jsr:@std/fs@^1.0.19", "jsr:@std/fs@^1.0.21",
"jsr:@std/http@^1.0.21", "jsr:@std/http@^1.0.23",
"jsr:@std/media-types@^1.1.0", "jsr:@std/media-types@^1.1.0",
"jsr:@std/path@^1.1.2" "jsr:@std/path@^1.1.4"
] ]
} }
} }

0
public/.spa Normal file
View file

View file

@ -1682,6 +1682,32 @@ body[data-perms*="files.write.own"] [data-requires-permission="files.write.own"]
left: 11px; left: 11px;
} }
.icon.map-pin {
box-sizing: border-box;
position: relative;
display: block;
transform: rotate(45deg) scale(var(--icon-scale, 1));
width: 18px;
height: 18px;
border-radius: 100% 100% 0 100%;
border: 2px solid;
margin-top: -4px;
}
.icon.map-pin::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 8px;
height: 8px;
border: 2px solid;
top: 3px;
left: 3px;
border-radius: 40px;
}
/* AUDIO PLAYER ICONS */ /* AUDIO PLAYER ICONS */
.icon.skip-back { .icon.skip-back {
box-sizing: border-box; box-sizing: border-box;

6
public/foo/index.html Normal file
View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<h1>Hello World - foo</h1>
</body>
</html>

View file

@ -5,11 +5,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>autonomous.contact</title> <title>autonomous.contact</title>
<link rel="apple-touch-icon" sizes="180x180" href="./icons/apple-touch-icon.png" ></link> <link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon.png" ></link>
<link rel="icon" type="image/png" sizes="32x32" href="./icons/favicon-32x32.png" ></link> <link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png" ></link>
<link rel="icon" type="image/png" sizes="16x16" href="./icons/favicon-16x16.png" ></link> <link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png" ></link>
<link rel="stylesheet" href="./base.css"></link> <link rel="stylesheet" href="/base.css"></link>
<!-- inlining these to force them to be scoped for everything else --> <!-- inlining these to force them to be scoped for everything else -->
<script type="text/javascript"><!-- #include file="./js/_utils.js" --></script> <script type="text/javascript"><!-- #include file="./js/_utils.js" --></script>
@ -17,31 +17,31 @@
<script type="text/javascript"><!-- #include file="./js/app.js" --></script> <script type="text/javascript"><!-- #include file="./js/app.js" --></script>
<!-- everything else --> <!-- everything else -->
<script src="./js/audioplayer.js" type="text/javascript"></script> <script src="/js/audioplayer.js" type="text/javascript"></script>
<script src="./js/datetimeutils.js" type="text/javascript"></script> <script src="/js/datetimeutils.js" type="text/javascript"></script>
<script src="./js/debounce.js" type="text/javascript"></script> <script src="/js/debounce.js" type="text/javascript"></script>
<script src="./js/external/md_to_html.js" type="text/javascript"></script> <script src="/js/external/md_to_html.js" type="text/javascript"></script>
<script src="./js/embeds/audio.js" type="text/javascript"></script> <script src="/js/embeds/audio.js" type="text/javascript"></script>
<script src="./js/embeds/gif.js" type="text/javascript"></script> <script src="/js/embeds/gif.js" type="text/javascript"></script>
<script src="./js/embeds/image.js" type="text/javascript"></script> <script src="/js/embeds/image.js" type="text/javascript"></script>
<script src="./js/embeds/link.js" type="text/javascript"></script> <script src="/js/embeds/link.js" type="text/javascript"></script>
<script src="./js/embeds/mkv.js" type="text/javascript"></script> <script src="/js/embeds/mkv.js" type="text/javascript"></script>
<script src="./js/embeds/mov.js" type="text/javascript"></script> <script src="/js/embeds/mov.js" type="text/javascript"></script>
<script src="./js/embeds/mp4.js" type="text/javascript"></script> <script src="/js/embeds/mp4.js" type="text/javascript"></script>
<script src="./js/embeds/spotify.js" type="text/javascript"></script> <script src="/js/embeds/spotify.js" type="text/javascript"></script>
<script src="./js/embeds/tidal.js" type="text/javascript"></script> <script src="/js/embeds/tidal.js" type="text/javascript"></script>
<script src="./js/embeds/vimeo.js" type="text/javascript"></script> <script src="/js/embeds/vimeo.js" type="text/javascript"></script>
<script src="./js/embeds/youtube.js" type="text/javascript"></script> <script src="/js/embeds/youtube.js" type="text/javascript"></script>
<script src="./js/emojis/en.js" type="text/javascript"></script> <script src="/js/emojis/en.js" type="text/javascript"></script>
<script src="./js/eventactions.js" type="text/javascript"></script> <script src="/js/eventactions.js" type="text/javascript"></script>
<script src="./js/htmlify.js" type="text/javascript"></script> <script src="/js/htmlify.js" type="text/javascript"></script>
<script src="./js/locationchange.js" type="text/javascript"></script> <script src="/js/locationchange.js" type="text/javascript"></script>
<script src="./js/notifications.js" type="text/javascript"></script> <script src="/js/notifications.js" type="text/javascript"></script>
<script src="./js/reactions.js" type="text/javascript"></script> <script src="/js/reactions.js" type="text/javascript"></script>
<script src="./js/smartfeeds.js" type="text/javascript"></script> <script src="/js/smartfeeds.js" type="text/javascript"></script>
<script src="./js/smartforms.js" type="text/javascript"></script> <script src="/js/smartforms.js" type="text/javascript"></script>
<script src="./js/textareaenhancements.js" type="text/javascript"></script> <script src="/js/textareaenhancements.js" type="text/javascript"></script>
<script src="./js/totp.js" type="text/javascript"></script> <script src="/js/totp.js" type="text/javascript"></script>
</head> </head>
<body> <body>

View file

@ -68,36 +68,10 @@ const APP = {
this._emit( 'view_changed', { this._emit( 'view_changed', {
previous, previous,
view view,
channel_id
}); });
} }
if (!document.body.dataset.channel || document.body.dataset.channel !== channel_id) {
const previous = typeof document.body.dataset.channel === 'string' ? document.body.dataset.channel : undefined;
if ( channel_id ) {
document.body.dataset.channel = channel_id;
}
else {
delete document.body.dataset.channel;
}
const target_channel_id = channel_id ?? this.CHANNELS.CHANNEL_LIST[0]?.id;
// TODO: allow a different default than chat
const hash_target = `/${ view ? view : 'chat' }` + ( target_channel_id ? `/channel/${ target_channel_id }` : '' );
if ( window.location.hash?.slice( 1 ) !== hash_target ) {
if ( previous !== target_channel_id ) {
this._emit( 'channel_changed', {
previous,
channel_id: target_channel_id
});
}
window.location.hash = hash_target;
}
}
}, },
load: async function() { load: async function() {
@ -236,22 +210,11 @@ const APP = {
const channels_response = await api.fetch("/api/channels"); const channels_response = await api.fetch("/api/channels");
if (channels_response.ok) { if (channels_response.ok) {
const new_channels = await channels_response.json(); const new_channels = await channels_response.json();
const has_differences =
APP.CHANNELS.CHANNEL_LIST.length !== new_channels.length ||
new_channels.some((channel, index) => {
return (
APP.CHANNELS.CHANNEL_LIST[index]?.id !== channel.id ||
APP.CHANNELS.CHANNEL_LIST[index]?.name !== channel.name
);
});
if (has_differences) {
APP.CHANNELS.CHANNEL_LIST = [...new_channels]; APP.CHANNELS.CHANNEL_LIST = [...new_channels];
APP._emit( 'channels_updated', { APP._emit( 'channels_updated', {
channels: APP.CHANNELS.CHANNEL_LIST channels: APP.CHANNELS.CHANNEL_LIST
}); });
}
APP.CHANNELS._last_channel_update = now; APP.CHANNELS._last_channel_update = now;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

14512
public/js/external/leaflet/leaflet-src.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

661
public/js/external/leaflet/leaflet.css vendored Normal file
View file

@ -0,0 +1,661 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}

6
public/js/external/leaflet/leaflet.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -19,6 +19,7 @@
#login-tab .tab-content { #login-tab .tab-content {
min-height: 17rem; min-height: 17rem;
overflow: hidden;
} }
#signup-tab .tab-content { #signup-tab .tab-content {

View file

@ -1,8 +1,19 @@
<script> <script>
APP.on("channels_updated", ({ channels }) => { APP.on("channels_updated", ({ channels }) => {
const channel_list = document.getElementById("channel-list"); const channel_list = document.getElementById("channel-list");
if ( !channel_list ) {
return;
}
channel_list.innerHTML = ""; channel_list.innerHTML = "";
for (const channel of channels.sort((lhs, rhs) => lhs.name.localeCompare(rhs.name))) { for (const channel of channels.sort((lhs, rhs) => lhs.name.localeCompare(rhs.name))) {
if ( !document.body.dataset.channel ) {
document.body.dataset.channel = APP.user?.meta?.chat?.last_channel ?? channel.id;
if ( APP.view === 'chat' ) {
window.location.hash = '/chat/channel/' + document.body.dataset.channel;
}
}
channel_list.insertAdjacentHTML( channel_list.insertAdjacentHTML(
"beforeend", "beforeend",
`<li id="channel-selector-${channel.id}" class="channel" data-channel-selector-for="${channel.id}"><a href="#/channel/${channel.id}/chat">${channel.name}</a></li>`, `<li id="channel-selector-${channel.id}" class="channel" data-channel-selector-for="${channel.id}"><a href="#/channel/${channel.id}/chat">${channel.name}</a></li>`,
@ -10,6 +21,36 @@
} }
}); });
APP.on( 'view_changed', ( { previous, view, channel_id } ) => {
if (document.body.dataset.channel !== channel_id) {
const previous_channel = typeof document.body.dataset.channel === 'string' ? document.body.dataset.channel : undefined;
if ( channel_id ) {
document.body.dataset.channel = channel_id;
}
else {
delete document.body.dataset.channel;
}
const target_channel_id = channel_id ?? this.CHANNELS.CHANNEL_LIST[0]?.id;
// TODO: allow a different default than chat
const hash_target = `/${ view ? view : 'chat' }` + ( target_channel_id ? `/channel/${ target_channel_id }` : '' );
if ( window.location.hash?.slice( 1 ) !== hash_target ) {
if ( previous_channel !== target_channel_id ) {
this._emit( 'channel_changed', {
previous: previous_channel,
channel_id: target_channel_id
});
}
window.location.hash = hash_target;
}
}
});
function update_channel_indicators(event) { function update_channel_indicators(event) {
document document
.querySelectorAll("[data-channel-selector-for]") .querySelectorAll("[data-channel-selector-for]")
@ -77,20 +118,25 @@
} }
}); });
APP.on( 'view_changed', ( {view} ) => { APP.on( 'view_changed', ( {previous, view} ) => {
if ( !view === 'chat' ) {
return;
}
const sidebar_dynamic_container = document.getElementById( 'sidebar-dynamic-container'); const sidebar_dynamic_container = document.getElementById( 'sidebar-dynamic-container');
if ( !sidebar_dynamic_container ) { if ( !sidebar_dynamic_container ) {
console.error( 'could not get #sidebar-dynamic-container' ); console.error( 'could not get #sidebar-dynamic-container' );
return; return;
} }
if ( view !== 'chat' && previous === 'chat' ) {
sidebar_dynamic_container.innerHTML = '';
delete document.body.dataset.channel;
return;
}
else if ( view !== 'chat' ) {
return;
}
const template = document.getElementById( 'channel-list-template'); const template = document.getElementById( 'channel-list-template');
sidebar_dynamic_container.innerHTML = template.innerHTML.trim(); sidebar_dynamic_container.innerHTML = template.innerHTML.trim();
APP.CHANNELS.update(); APP.CHANNELS.update(true);
}); });
</script> </script>
<style> <style>

55
public/tabs/map/map.html Normal file
View file

@ -0,0 +1,55 @@
<link rel="stylesheet" href="/js/external/leaflet/leaflet.css"/>
<script src="/js/external/leaflet/leaflet.js"></script>
<script type="module">
var map = L.map('map').setView([33.88,-118.13], 13);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
APP.on( "view_changed", ({ view }) => {
if ( view === 'map' ) {
map.invalidateSize();
}
});
</script>
<style>
#map-tab .tab-content {
overflow: hidden;
}
#map {
position: absolute;
top: 4em;
left: 4em;
right: 4em;
bottom: 4em;
}
@media screen and (max-width: 640px) {
#map {
top: 1em;
left: 1em;
right: 1em;
bottom: 1em;
}
}
</style>
<div id="map-tab" class="tab">
<input
type="radio"
name="top-level-tabs"
id="map-tab-tab-input"
class="tab-switch"
data-view="map"
/>
<label for="map-tab-tab-input" class="tab-label"
><div class="icon map-pin"></div>
<div class="label">Map</div>
</label>
<div class="tab-content">
<div id="map"></div>
</div>
</div>

View file

@ -13,6 +13,7 @@
tab_switch.addEventListener("input", (event) => { tab_switch.addEventListener("input", (event) => {
const tab_selector = event.target; const tab_selector = event.target;
const view = tab_selector.dataset.view; const view = tab_selector.dataset.view;
if (view) { if (view) {
window.location.hash = `/${view}${ document.body.dataset.channel ? `/channel/${ document.body.dataset.channel }` : '' }`; window.location.hash = `/${view}${ document.body.dataset.channel ? `/channel/${ document.body.dataset.channel }` : '' }`;
} }
@ -134,4 +135,5 @@
<!-- #include file="./blurbs/blurbs.html" --> <!-- #include file="./blurbs/blurbs.html" -->
<!-- #include file="./forum/forum.html" --> <!-- #include file="./forum/forum.html" -->
<!-- #include file="./essays/essays.html" --> <!-- #include file="./essays/essays.html" -->
<!-- #include file="./map/map.html" -->
</div> </div>