Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33af39ee93 | |||
| 1d56a2193d | |||
| 75905e4652 | |||
| d07b9cde5f | |||
| d8a9cc5a7e |
@@ -4,6 +4,14 @@
|
|||||||
|
|
||||||
* Add alerts via Anchor
|
* Add alerts via Anchor
|
||||||
|
|
||||||
|
# 0.2.14
|
||||||
|
|
||||||
|
* Add calendar event editing
|
||||||
|
|
||||||
|
# 0.2.13
|
||||||
|
|
||||||
|
* Fix android keyboard issue
|
||||||
|
|
||||||
# 0.2.12
|
# 0.2.12
|
||||||
|
|
||||||
* Fix keyboard covering chat input
|
* Fix keyboard covering chat input
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "social.flotilla"
|
applicationId "social.flotilla"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 12
|
versionCode 13
|
||||||
versionName "0.2.12"
|
versionName "0.2.13"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ android {
|
|||||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':capacitor-app')
|
implementation project(':capacitor-app')
|
||||||
|
implementation project(':capacitor-keyboard')
|
||||||
implementation project(':nostr-signer-capacitor-plugin')
|
implementation project(':nostr-signer-capacitor-plugin')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/title_activity_main"
|
android:label="@string/title_activity_main"
|
||||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -5,5 +5,8 @@ project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/
|
|||||||
include ':capacitor-app'
|
include ':capacitor-app'
|
||||||
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
||||||
|
|
||||||
|
include ':capacitor-keyboard'
|
||||||
|
project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android')
|
||||||
|
|
||||||
include ':nostr-signer-capacitor-plugin'
|
include ':nostr-signer-capacitor-plugin'
|
||||||
project(':nostr-signer-capacitor-plugin').projectDir = new File('../node_modules/nostr-signer-capacitor-plugin/android')
|
project(':nostr-signer-capacitor-plugin').projectDir = new File('../node_modules/nostr-signer-capacitor-plugin/android')
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ const config: CapacitorConfig = {
|
|||||||
SplashScreen: {
|
SplashScreen: {
|
||||||
androidSplashResourceName: "splash"
|
androidSplashResourceName: "splash"
|
||||||
},
|
},
|
||||||
|
Keyboard: {
|
||||||
|
style: "DARK",
|
||||||
|
resizeOnFullScreen: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Use this for live reload https://capacitorjs.com/docs/guides/live-reload
|
// Use this for live reload https://capacitorjs.com/docs/guides/live-reload
|
||||||
// server: {
|
// server: {
|
||||||
|
|||||||
@@ -351,14 +351,14 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 6;
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 0.2.12;
|
MARKETING_VERSION = 0.2.13;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -376,14 +376,14 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 6;
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 0.2.12;
|
MARKETING_VERSION = 0.2.13;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ def capacitor_pods
|
|||||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
||||||
|
pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
|
||||||
pod 'NostrSignerCapacitorPlugin', :path => '../../node_modules/nostr-signer-capacitor-plugin'
|
pod 'NostrSignerCapacitorPlugin', :path => '../../node_modules/nostr-signer-capacitor-plugin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Generated
+10
@@ -13,6 +13,7 @@
|
|||||||
"@capacitor/cli": "^7.0.0",
|
"@capacitor/cli": "^7.0.0",
|
||||||
"@capacitor/core": "^7.0.1",
|
"@capacitor/core": "^7.0.1",
|
||||||
"@capacitor/ios": "^7.0.0",
|
"@capacitor/ios": "^7.0.0",
|
||||||
|
"@capacitor/keyboard": "^7.0.0",
|
||||||
"@noble/curves": "^1.5.0",
|
"@noble/curves": "^1.5.0",
|
||||||
"@noble/hashes": "^1.4.0",
|
"@noble/hashes": "^1.4.0",
|
||||||
"@poppanator/sveltekit-svg": "^4.2.1",
|
"@poppanator/sveltekit-svg": "^4.2.1",
|
||||||
@@ -2090,6 +2091,15 @@
|
|||||||
"@capacitor/core": "^7.0.0"
|
"@capacitor/core": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@capacitor/keyboard": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tqwy8wG+sx4UqiFCX4Q+bFw6uKgG7BiHKAPpeefoIgoEB8H8Jf3xZNZoVPnJIMuPsCdSvuyHXZbJXH9IEEirGA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@capacitor/core": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@cspotcode/source-map-support": {
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "flotilla",
|
"name": "flotilla",
|
||||||
"version": "0.2.12",
|
"version": "0.2.13",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
"@capacitor/cli": "^7.0.0",
|
"@capacitor/cli": "^7.0.0",
|
||||||
"@capacitor/core": "^7.0.1",
|
"@capacitor/core": "^7.0.1",
|
||||||
"@capacitor/ios": "^7.0.0",
|
"@capacitor/ios": "^7.0.0",
|
||||||
|
"@capacitor/keyboard": "^7.0.0",
|
||||||
"@noble/curves": "^1.5.0",
|
"@noble/curves": "^1.5.0",
|
||||||
"@noble/hashes": "^1.4.0",
|
"@noble/hashes": "^1.4.0",
|
||||||
"@poppanator/sveltekit-svg": "^4.2.1",
|
"@poppanator/sveltekit-svg": "^4.2.1",
|
||||||
|
|||||||
+2
-2
@@ -341,11 +341,11 @@ progress[value]::-webkit-progress-value {
|
|||||||
/* chat view */
|
/* chat view */
|
||||||
|
|
||||||
.chat__page-bar {
|
.chat__page-bar {
|
||||||
@apply sait cw !fixed top-0;
|
@apply sait cw !fixed top-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__messages {
|
.chat__messages {
|
||||||
@apply saib cw fixed top-12 flex h-[calc(100%-10rem)] flex-col-reverse overflow-y-auto overflow-x-hidden md:h-[calc(100%-6rem)];
|
@apply saib cw fixed top-12 flex h-[calc(100%-6rem)] flex-col-reverse overflow-y-auto overflow-x-hidden md:h-[calc(100%-3rem)];
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__compose {
|
.chat__compose {
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {pubkey} from "@welshman/app"
|
import {pubkey} from "@welshman/app"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||||
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
||||||
import EventActivity from "@app/components/EventActivity.svelte"
|
import EventActivity from "@app/components/EventActivity.svelte"
|
||||||
import EventActions from "@app/components/EventActions.svelte"
|
import EventActions from "@app/components/EventActions.svelte"
|
||||||
|
import CalendarEventEdit from "@app/components/CalendarEventEdit.svelte"
|
||||||
import {publishDelete, publishReaction} from "@app/commands"
|
import {publishDelete, publishReaction} from "@app/commands"
|
||||||
import {makeCalendarPath} from "@app/routes"
|
import {makeCalendarPath} from "@app/routes"
|
||||||
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
url,
|
url,
|
||||||
@@ -20,6 +24,8 @@
|
|||||||
|
|
||||||
const path = makeCalendarPath(url, event.id)
|
const path = makeCalendarPath(url, event.id)
|
||||||
|
|
||||||
|
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
||||||
|
|
||||||
const onReactionClick = (content: string, events: TrustedEvent[]) => {
|
const onReactionClick = (content: string, events: TrustedEvent[]) => {
|
||||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
const reaction = events.find(e => e.pubkey === $pubkey)
|
||||||
|
|
||||||
@@ -38,6 +44,17 @@
|
|||||||
{#if showActivity}
|
{#if showActivity}
|
||||||
<EventActivity {url} {path} {event} />
|
<EventActivity {url} {path} {event} />
|
||||||
{/if}
|
{/if}
|
||||||
<EventActions {url} {event} noun="Event" />
|
<EventActions {url} {event} noun="Event">
|
||||||
|
{#snippet customActions()}
|
||||||
|
{#if event.pubkey === $pubkey}
|
||||||
|
<li>
|
||||||
|
<Button onclick={editEvent}>
|
||||||
|
<Icon size={4} icon="pen" />
|
||||||
|
Edit Event
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
</EventActions>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,164 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {writable} from "svelte/store"
|
|
||||||
import {randomId, HOUR} from "@welshman/lib"
|
|
||||||
import {createEvent, EVENT_TIME} from "@welshman/util"
|
|
||||||
import {publishThunk} from "@welshman/app"
|
|
||||||
import {preventDefault} from "@lib/html"
|
|
||||||
import {daysBetween} from "@lib/util"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
|
||||||
import Field from "@lib/components/Field.svelte"
|
|
||||||
import Button from "@lib/components/Button.svelte"
|
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import CalendarEventForm from "@app/components/CalendarEventForm.svelte"
|
||||||
import DateTimeInput from "@lib/components/DateTimeInput.svelte"
|
|
||||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
|
||||||
import {PROTECTED, GENERAL, tagRoom} from "@app/state"
|
|
||||||
import {makeEditor} from "@app/editor"
|
|
||||||
import {pushToast} from "@app/toast"
|
|
||||||
|
|
||||||
const {url} = $props()
|
type Props = {
|
||||||
|
url: string
|
||||||
const uploading = writable(false)
|
|
||||||
|
|
||||||
const back = () => history.back()
|
|
||||||
|
|
||||||
const submit = () => {
|
|
||||||
if ($uploading) return
|
|
||||||
|
|
||||||
if (!title) {
|
|
||||||
return pushToast({
|
|
||||||
theme: "error",
|
|
||||||
message: "Please provide a title.",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!start || !end) {
|
|
||||||
return pushToast({
|
|
||||||
theme: "error",
|
|
||||||
message: "Please provide start and end times.",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start >= end) {
|
|
||||||
return pushToast({
|
|
||||||
theme: "error",
|
|
||||||
message: "End time must be later than start time.",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = createEvent(EVENT_TIME, {
|
|
||||||
content: editor.getText({blockSeparator: "\n"}).trim(),
|
|
||||||
tags: [
|
|
||||||
["d", randomId()],
|
|
||||||
["title", title],
|
|
||||||
["location", location],
|
|
||||||
["start", start.toString()],
|
|
||||||
["end", end.toString()],
|
|
||||||
...daysBetween(start, end).map(D => ["D", D.toString()]),
|
|
||||||
...editor.storage.nostr.getEditorTags(),
|
|
||||||
tagRoom(GENERAL, url),
|
|
||||||
PROTECTED,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
pushToast({message: "Your event has been published!"})
|
|
||||||
publishThunk({event, relays: [url]})
|
|
||||||
history.back()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const editor = makeEditor({submit, uploading})
|
const {url}: Props = $props()
|
||||||
|
|
||||||
let title = $state("")
|
|
||||||
let location = $state("")
|
|
||||||
let start: number | undefined = $state()
|
|
||||||
let end: number | undefined = $state()
|
|
||||||
let endDirty = false
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (!endDirty && start) {
|
|
||||||
end = start + HOUR
|
|
||||||
} else if (end) {
|
|
||||||
endDirty = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="column gap-4" onsubmit={preventDefault(submit)}>
|
<CalendarEventForm {url}>
|
||||||
<ModalHeader>
|
{#snippet header()}
|
||||||
{#snippet title()}
|
<ModalHeader>
|
||||||
<div>Create an Event</div>
|
{#snippet title()}
|
||||||
{/snippet}
|
<div>Create an Event</div>
|
||||||
{#snippet info()}
|
{/snippet}
|
||||||
<div>Invite other group members to events online or in real life.</div>
|
{#snippet info()}
|
||||||
{/snippet}
|
<div>Invite other group members to events online or in real life.</div>
|
||||||
</ModalHeader>
|
{/snippet}
|
||||||
<Field>
|
</ModalHeader>
|
||||||
{#snippet label()}
|
{/snippet}
|
||||||
<p>Title*</p>
|
</CalendarEventForm>
|
||||||
{/snippet}
|
|
||||||
{#snippet input()}
|
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
|
||||||
<input bind:value={title} class="grow" type="text" />
|
|
||||||
</label>
|
|
||||||
{/snippet}
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
{#snippet label()}
|
|
||||||
<p>Summary</p>
|
|
||||||
{/snippet}
|
|
||||||
{#snippet input()}
|
|
||||||
<div class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
|
|
||||||
<div class="input-editor flex-grow overflow-hidden">
|
|
||||||
<EditorContent {editor} />
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
data-tip="Add an image"
|
|
||||||
class="center btn tooltip"
|
|
||||||
onclick={() => editor.chain().selectFiles().run()}>
|
|
||||||
{#if $uploading}
|
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
|
||||||
{:else}
|
|
||||||
<Icon icon="gallery-send" />
|
|
||||||
{/if}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{/snippet}
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
{#snippet label()}
|
|
||||||
Start*
|
|
||||||
{/snippet}
|
|
||||||
{#snippet input()}
|
|
||||||
<DateTimeInput bind:value={start} />
|
|
||||||
{/snippet}
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
{#snippet label()}
|
|
||||||
End*
|
|
||||||
{/snippet}
|
|
||||||
{#snippet input()}
|
|
||||||
<DateTimeInput bind:value={end} />
|
|
||||||
{/snippet}
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
{#snippet label()}
|
|
||||||
<p>Location (optional)</p>
|
|
||||||
{/snippet}
|
|
||||||
{#snippet input()}
|
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
|
||||||
<Icon icon="map-point" />
|
|
||||||
<input bind:value={location} class="grow" type="text" />
|
|
||||||
</label>
|
|
||||||
{/snippet}
|
|
||||||
</Field>
|
|
||||||
<ModalFooter>
|
|
||||||
<Button class="btn btn-link" onclick={back}>
|
|
||||||
<Icon icon="alt-arrow-left" />
|
|
||||||
Go back
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" class="btn btn-primary" disabled={$uploading}>
|
|
||||||
<Spinner loading={$uploading}>Create Event</Spinner>
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</form>
|
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex h-14 w-14 flex-col items-center justify-center gap-1 rounded-box border border-solid border-base-content p-2 sm:h-24 sm:w-24">
|
class="hidden h-32 w-32 min-w-32 flex-col items-center justify-center gap-1 rounded-box bg-base-300 p-2 sm:flex">
|
||||||
<span class="sm:text-lg">{Intl.DateTimeFormat(LOCALE, {month: "short"}).format(startDate)}</span>
|
<strong>{Intl.DateTimeFormat(LOCALE, {month: "short"}).format(startDate)}</strong>
|
||||||
<span class="sm:text-4xl">{Intl.DateTimeFormat(LOCALE, {day: "numeric"}).format(startDate)}</span>
|
<span class="text-4xl">{Intl.DateTimeFormat(LOCALE, {day: "numeric"}).format(startDate)}</span>
|
||||||
|
<span class="text-xs opacity-75"
|
||||||
|
>{Intl.DateTimeFormat(LOCALE, {weekday: "long"}).format(startDate)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
import {getTagValue} from "@welshman/util"
|
||||||
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
|
import CalendarEventForm from "@app/components/CalendarEventForm.svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
url: string
|
||||||
|
event: TrustedEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url, event}: Props = $props()
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
d: getTagValue("d", event.tags)!,
|
||||||
|
title: getTagValue("title", event.tags)!,
|
||||||
|
location: getTagValue("location", event.tags)!,
|
||||||
|
start: parseInt(getTagValue("start", event.tags)!),
|
||||||
|
end: parseInt(getTagValue("end", event.tags)!),
|
||||||
|
content: event.content,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarEventForm {url} {initialValues}>
|
||||||
|
{#snippet header()}
|
||||||
|
<ModalHeader>
|
||||||
|
{#snippet title()}
|
||||||
|
<div>Edit this Event</div>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet info()}
|
||||||
|
<div>Invite other group members to events online or in real life.</div>
|
||||||
|
{/snippet}
|
||||||
|
</ModalHeader>
|
||||||
|
{/snippet}
|
||||||
|
</CalendarEventForm>
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {Snippet} from "svelte"
|
||||||
|
import {writable} from "svelte/store"
|
||||||
|
import {randomId, HOUR} from "@welshman/lib"
|
||||||
|
import {createEvent, EVENT_TIME} from "@welshman/util"
|
||||||
|
import {publishThunk} from "@welshman/app"
|
||||||
|
import {preventDefault} from "@lib/html"
|
||||||
|
import {daysBetween} from "@lib/util"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Field from "@lib/components/Field.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
|
import DateTimeInput from "@lib/components/DateTimeInput.svelte"
|
||||||
|
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||||
|
import {PROTECTED, GENERAL, tagRoom} from "@app/state"
|
||||||
|
import {makeEditor} from "@app/editor"
|
||||||
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
url: string
|
||||||
|
header: Snippet
|
||||||
|
initialValues?: {
|
||||||
|
d: string
|
||||||
|
title: string
|
||||||
|
content: string
|
||||||
|
location: string
|
||||||
|
start: number
|
||||||
|
end: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url, header, initialValues}: Props = $props()
|
||||||
|
|
||||||
|
const uploading = writable(false)
|
||||||
|
|
||||||
|
const back = () => history.back()
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
if ($uploading) return
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
return pushToast({
|
||||||
|
theme: "error",
|
||||||
|
message: "Please provide a title.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!start || !end) {
|
||||||
|
return pushToast({
|
||||||
|
theme: "error",
|
||||||
|
message: "Please provide start and end times.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start >= end) {
|
||||||
|
return pushToast({
|
||||||
|
theme: "error",
|
||||||
|
message: "End time must be later than start time.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = createEvent(EVENT_TIME, {
|
||||||
|
content: editor.getText({blockSeparator: "\n"}).trim(),
|
||||||
|
tags: [
|
||||||
|
["d", initialValues?.d || randomId()],
|
||||||
|
["title", title],
|
||||||
|
["location", location],
|
||||||
|
["start", start.toString()],
|
||||||
|
["end", end.toString()],
|
||||||
|
...daysBetween(start, end).map(D => ["D", D.toString()]),
|
||||||
|
...editor.storage.nostr.getEditorTags(),
|
||||||
|
tagRoom(GENERAL, url),
|
||||||
|
PROTECTED,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
pushToast({message: "Your event has been saved!"})
|
||||||
|
publishThunk({event, relays: [url]})
|
||||||
|
history.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = initialValues?.content || ""
|
||||||
|
const editor = makeEditor({submit, uploading, content})
|
||||||
|
|
||||||
|
let title = $state(initialValues?.title)
|
||||||
|
let location = $state(initialValues?.location)
|
||||||
|
let start: number | undefined = $state(initialValues?.start)
|
||||||
|
let end: number | undefined = $state(initialValues?.end)
|
||||||
|
let endDirty = Boolean(initialValues?.end)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!endDirty && start) {
|
||||||
|
end = start + HOUR
|
||||||
|
} else if (end) {
|
||||||
|
endDirty = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form novalidate class="column gap-4" onsubmit={preventDefault(submit)}>
|
||||||
|
{@render header()}
|
||||||
|
<Field>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Title*</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
|
<input bind:value={title} class="grow" type="text" />
|
||||||
|
</label>
|
||||||
|
{/snippet}
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Summary</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<div class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
|
||||||
|
<div class="input-editor flex-grow overflow-hidden">
|
||||||
|
<EditorContent {editor} />
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
data-tip="Add an image"
|
||||||
|
class="center btn tooltip"
|
||||||
|
onclick={() => editor.chain().selectFiles().run()}>
|
||||||
|
{#if $uploading}
|
||||||
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
|
{:else}
|
||||||
|
<Icon icon="gallery-send" />
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
{#snippet label()}
|
||||||
|
Start*
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<DateTimeInput bind:value={start} />
|
||||||
|
{/snippet}
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
{#snippet label()}
|
||||||
|
End*
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<DateTimeInput bind:value={end} />
|
||||||
|
{/snippet}
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Location (optional)</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
|
<Icon icon="map-point" />
|
||||||
|
<input bind:value={location} class="grow" type="text" />
|
||||||
|
</label>
|
||||||
|
{/snippet}
|
||||||
|
</Field>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button class="btn btn-link" onclick={back}>
|
||||||
|
<Icon icon="alt-arrow-left" />
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" class="btn btn-primary" disabled={$uploading}>
|
||||||
|
<Spinner loading={$uploading}>Save Event</Spinner>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</form>
|
||||||
@@ -17,8 +17,13 @@
|
|||||||
const isSingleDay = $derived(startDateDisplay === endDateDisplay)
|
const isSingleDay = $derived(startDateDisplay === endDateDisplay)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p class="text-xl">{meta.title || meta.name}</p>
|
<div class="flex flex-grow flex-wrap justify-between gap-2">
|
||||||
<div class="flex items-center gap-2 text-sm">
|
<p class="text-xl">{meta.title || meta.name}</p>
|
||||||
<Icon icon="clock-circle" size={4} />
|
<div class="flex items-center gap-2 text-sm">
|
||||||
{formatTimestampAsTime(start)} — {isSingleDay ? formatTimestampAsTime(end) : formatTimestamp(end)}
|
<Icon icon="clock-circle" size={4} />
|
||||||
|
<span class="sm:hidden">{formatTimestampAsDate(start)}</span>
|
||||||
|
{formatTimestampAsTime(start)} — {isSingleDay
|
||||||
|
? formatTimestampAsTime(end)
|
||||||
|
: formatTimestamp(end)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,9 +15,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link class="col-3 card2 bg-alt w-full cursor-pointer" href={makeCalendarPath(url, event.id)}>
|
<Link class="col-3 card2 bg-alt w-full cursor-pointer" href={makeCalendarPath(url, event.id)}>
|
||||||
<div class="flex items-center justify-between gap-2">
|
<CalendarEventHeader {event} />
|
||||||
<CalendarEventHeader {event} />
|
|
||||||
</div>
|
|
||||||
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
|
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
|
||||||
<span class="whitespace-nowrap py-1 text-sm opacity-75">
|
<span class="whitespace-nowrap py-1 text-sm opacity-75">
|
||||||
Posted by <ProfileLink pubkey={event.pubkey} />
|
Posted by <ProfileLink pubkey={event.pubkey} />
|
||||||
|
|||||||
@@ -12,13 +12,15 @@
|
|||||||
const meta = $derived(fromPairs(event.tags) as Record<string, string>)
|
const meta = $derived(fromPairs(event.tags) as Record<string, string>)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span>
|
<div class="flex min-w-0 flex-col gap-1 text-sm opacity-75">
|
||||||
Posted by <ProfileLink pubkey={event.pubkey} />
|
|
||||||
</span>
|
|
||||||
{#if meta.location}
|
|
||||||
<span>•</span>
|
|
||||||
<span class="flex items-center gap-1">
|
<span class="flex items-center gap-1">
|
||||||
<Icon icon="map-point" size={4} />
|
<Icon icon="user-circle" size={4} />
|
||||||
{meta.location}
|
Posted by <ProfileLink pubkey={event.pubkey} />
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{#if meta.location}
|
||||||
|
<span class="flex items-start gap-1">
|
||||||
|
<Icon icon="map-point" class="mt-[2px]" size={4} />
|
||||||
|
<span class="break-words">{meta.location}</span>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
let loading = $state(true)
|
let loading = $state(true)
|
||||||
let compose: ChatCompose | undefined = $state()
|
let compose: ChatCompose | undefined = $state()
|
||||||
let parent: TrustedEvent | undefined = $state()
|
let parent: TrustedEvent | undefined = $state()
|
||||||
let parentPreview: HTMLElement | undefined = $state()
|
let chatCompose: HTMLElement | undefined = $state()
|
||||||
let dynamicPadding: HTMLElement | undefined = $state()
|
let dynamicPadding: HTMLElement | undefined = $state()
|
||||||
|
|
||||||
const elements = $derived.by(() => {
|
const elements = $derived.by(() => {
|
||||||
@@ -108,13 +108,13 @@
|
|||||||
load({filters: [{kinds: [INBOX_RELAYS], authors: others}]})
|
load({filters: [{kinds: [INBOX_RELAYS], authors: others}]})
|
||||||
|
|
||||||
const observer = new ResizeObserver(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
dynamicPadding!.style.minHeight = `${parentPreview!.offsetHeight}px`
|
dynamicPadding!.style.minHeight = `${chatCompose!.offsetHeight}px`
|
||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(parentPreview!)
|
observer.observe(chatCompose!)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
observer.unobserve(parentPreview!)
|
observer.unobserve(chatCompose!)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -225,8 +225,8 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chat__compose bg-base-200">
|
<div class="chat__compose bg-base-200" bind:this={chatCompose}>
|
||||||
<div bind:this={parentPreview}>
|
<div>
|
||||||
{#if parent}
|
{#if parent}
|
||||||
<ChatComposeParent event={parent} clear={clearParent} verb="Replying to" />
|
<ChatComposeParent event={parent} clear={clearParent} verb="Replying to" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type {Snippet} from "svelte"
|
||||||
import type {Instance} from "tippy.js"
|
import type {Instance} from "tippy.js"
|
||||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
@@ -9,15 +10,14 @@
|
|||||||
import EventMenu from "@app/components/EventMenu.svelte"
|
import EventMenu from "@app/components/EventMenu.svelte"
|
||||||
import {publishReaction} from "@app/commands"
|
import {publishReaction} from "@app/commands"
|
||||||
|
|
||||||
const {
|
type Props = {
|
||||||
url,
|
|
||||||
noun,
|
|
||||||
event,
|
|
||||||
}: {
|
|
||||||
url: string
|
url: string
|
||||||
noun: string
|
noun: string
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
} = $props()
|
customActions?: Snippet
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url, noun, event, customActions}: Props = $props()
|
||||||
|
|
||||||
const showPopover = () => popover?.show()
|
const showPopover = () => popover?.show()
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
component={EventMenu}
|
component={EventMenu}
|
||||||
props={{url, noun, event, onClick: hidePopover}}
|
props={{url, noun, event, customActions, onClick: hidePopover}}
|
||||||
params={{trigger: "manual", interactive: true}}>
|
params={{trigger: "manual", interactive: true}}>
|
||||||
<Button class="btn join-item btn-neutral btn-xs" onclick={showPopover}>
|
<Button class="btn join-item btn-neutral btn-xs" onclick={showPopover}>
|
||||||
<Icon icon="menu-dots" size={4} />
|
<Icon icon="menu-dots" size={4} />
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {onMount} from "svelte"
|
||||||
|
import type {Snippet} from "svelte"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {COMMENT} from "@welshman/util"
|
import {COMMENT} from "@welshman/util"
|
||||||
import {pubkey} from "@welshman/app"
|
import {pubkey} from "@welshman/app"
|
||||||
@@ -10,42 +12,34 @@
|
|||||||
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
const {
|
type Props = {
|
||||||
url,
|
|
||||||
noun,
|
|
||||||
event,
|
|
||||||
onClick,
|
|
||||||
}: {
|
|
||||||
url: string
|
url: string
|
||||||
noun: string
|
noun: string
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
} = $props()
|
customActions?: Snippet
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url, noun, event, onClick, customActions}: Props = $props()
|
||||||
|
|
||||||
const isRoot = event.kind !== COMMENT
|
const isRoot = event.kind !== COMMENT
|
||||||
|
|
||||||
const report = () => {
|
const report = () => pushModal(EventReport, {url, event})
|
||||||
onClick()
|
|
||||||
pushModal(EventReport, {url, event})
|
|
||||||
}
|
|
||||||
|
|
||||||
const showInfo = () => {
|
const showInfo = () => pushModal(EventInfo, {url, event})
|
||||||
onClick()
|
|
||||||
pushModal(EventInfo, {url, event})
|
|
||||||
}
|
|
||||||
|
|
||||||
const share = () => {
|
const share = () => pushModal(EventShare, {url, event})
|
||||||
onClick()
|
|
||||||
pushModal(EventShare, {url, event})
|
|
||||||
}
|
|
||||||
|
|
||||||
const showDelete = () => {
|
const showDelete = () => pushModal(EventDeleteConfirm, {url, event})
|
||||||
onClick()
|
|
||||||
pushModal(EventDeleteConfirm, {url, event})
|
let ul: Element
|
||||||
}
|
|
||||||
|
onMount(() => {
|
||||||
|
ul.addEventListener("click", onClick)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-xl">
|
<ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-xl" bind:this={ul}>
|
||||||
{#if isRoot}
|
{#if isRoot}
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={share}>
|
<Button onclick={share}>
|
||||||
@@ -60,6 +54,7 @@
|
|||||||
{noun} Details
|
{noun} Details
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
|
{@render customActions?.()}
|
||||||
{#if event.pubkey === $pubkey}
|
{#if event.pubkey === $pubkey}
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={showDelete} class="text-error">
|
<Button onclick={showDelete} class="text-error">
|
||||||
|
|||||||
@@ -12,9 +12,7 @@
|
|||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<CalendarEventDate event={props.event} />
|
<CalendarEventDate event={props.event} />
|
||||||
<div class="flex flex-grow flex-col">
|
<div class="flex flex-grow flex-col">
|
||||||
<div class="flex flex-grow flex-wrap justify-between gap-2">
|
<CalendarEventHeader event={props.event} />
|
||||||
<CalendarEventHeader event={props.event} />
|
|
||||||
</div>
|
|
||||||
<div class="flex py-2 opacity-50">
|
<div class="flex py-2 opacity-50">
|
||||||
<div class="h-px flex-grow bg-base-content opacity-25"></div>
|
<div class="h-px flex-grow bg-base-content opacity-25"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+3
-1
@@ -23,6 +23,7 @@ import {
|
|||||||
matchFilters,
|
matchFilters,
|
||||||
getTagValues,
|
getTagValues,
|
||||||
getTagValue,
|
getTagValue,
|
||||||
|
getAddress,
|
||||||
isShareableRelayUrl,
|
isShareableRelayUrl,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent, Filter, List} from "@welshman/util"
|
import type {TrustedEvent, Filter, List} from "@welshman/util"
|
||||||
@@ -214,6 +215,7 @@ export const makeCalendarFeed = ({
|
|||||||
|
|
||||||
const insertEvent = (event: TrustedEvent) => {
|
const insertEvent = (event: TrustedEvent) => {
|
||||||
const start = getStart(event)
|
const start = getStart(event)
|
||||||
|
const address = getAddress(event)
|
||||||
|
|
||||||
if (isNaN(start) || isNaN(getEnd(event))) return
|
if (isNaN(start) || isNaN(getEnd(event))) return
|
||||||
|
|
||||||
@@ -223,7 +225,7 @@ export const makeCalendarFeed = ({
|
|||||||
if (getStart($events[i]) > start) return insert(i, event, $events)
|
if (getStart($events[i]) > start) return insert(i, event, $events)
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...$events, event]
|
return [...$events.filter(e => getAddress(e) !== address), event]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
const pad = (n: number) => ("00" + String(n)).slice(-2)
|
const pad = (n: number) => ("00" + String(n)).slice(-2)
|
||||||
|
|
||||||
const getTime = (d: Date) => `${pad(d.getHours())}:${minutes}`
|
const getTime = (d: Date, m: string) => `${pad(d.getHours())}:${m}`
|
||||||
|
|
||||||
const setTime = (d: Date, time: string) => {
|
const setTime = (d: Date, time: string) => {
|
||||||
const [hours, minutes] = time.split(":").map(x => parseInt(x))
|
const [hours, minutes] = time.split(":").map(x => parseInt(x))
|
||||||
@@ -37,15 +37,19 @@
|
|||||||
time = undefined
|
time = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
let date: Date | undefined = $state()
|
const initialDate = value ? secondsToDate(value) : undefined
|
||||||
let time: string | undefined = $state()
|
const initialTime = initialDate ? getTime(initialDate, pad(initialDate.getMinutes())) : undefined
|
||||||
let minutes: string = $state("00")
|
const initialMinutes = initialTime ? initialTime.slice(-2) : "00"
|
||||||
|
|
||||||
|
let date: Date | undefined = $state(initialDate)
|
||||||
|
let time: string | undefined = $state(initialTime)
|
||||||
|
let minutes: string = $state(initialMinutes)
|
||||||
let element: HTMLElement
|
let element: HTMLElement
|
||||||
|
|
||||||
// Sync date to time and value
|
// Sync date to time and value
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (date) {
|
if (date) {
|
||||||
time = getTime(date)
|
time = getTime(date, minutes)
|
||||||
value = dateToSeconds(date)
|
value = dateToSeconds(date)
|
||||||
} else {
|
} else {
|
||||||
value = undefined
|
value = undefined
|
||||||
@@ -55,7 +59,7 @@
|
|||||||
// Sync updates to value to date/time
|
// Sync updates to value to date/time
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const derivedDate = value ? secondsToDate(value) : undefined
|
const derivedDate = value ? secondsToDate(value) : undefined
|
||||||
const derivedTime = derivedDate ? getTime(derivedDate) : undefined
|
const derivedTime = derivedDate ? getTime(derivedDate, minutes) : undefined
|
||||||
|
|
||||||
date = derivedDate
|
date = derivedDate
|
||||||
time = derivedTime
|
time = derivedTime
|
||||||
|
|||||||
@@ -134,7 +134,7 @@
|
|||||||
let parent: TrustedEvent | undefined = $state()
|
let parent: TrustedEvent | undefined = $state()
|
||||||
let element: HTMLElement | undefined = $state()
|
let element: HTMLElement | undefined = $state()
|
||||||
let newMessages: HTMLElement | undefined = $state()
|
let newMessages: HTMLElement | undefined = $state()
|
||||||
let parentPreview: HTMLElement | undefined = $state()
|
let chatCompose: HTMLElement | undefined = $state()
|
||||||
let dynamicPadding: HTMLElement | undefined = $state()
|
let dynamicPadding: HTMLElement | undefined = $state()
|
||||||
let newMessagesSeen = false
|
let newMessagesSeen = false
|
||||||
let showFixedNewMessages = $state(false)
|
let showFixedNewMessages = $state(false)
|
||||||
@@ -212,13 +212,13 @@
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const observer = new ResizeObserver(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
dynamicPadding!.style.minHeight = `${parentPreview!.offsetHeight}px`
|
dynamicPadding!.style.minHeight = `${chatCompose!.offsetHeight}px`
|
||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(parentPreview!)
|
observer.observe(chatCompose!)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
observer.unobserve(parentPreview!)
|
observer.unobserve(chatCompose!)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -301,8 +301,8 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chat__compose bg-base-200">
|
<div class="chat__compose bg-base-200" bind:this={chatCompose}>
|
||||||
<div bind:this={parentPreview}>
|
<div>
|
||||||
{#if parent}
|
{#if parent}
|
||||||
<ChannelComposeParent event={parent} clear={clearParent} verb="Replying to" />
|
<ChannelComposeParent event={parent} clear={clearParent} verb="Replying to" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -83,13 +83,9 @@
|
|||||||
<div class="card2 bg-alt col-3 z-feature">
|
<div class="card2 bg-alt col-3 z-feature">
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<CalendarEventDate event={$event} />
|
<CalendarEventDate event={$event} />
|
||||||
<div class="flex flex-grow flex-col">
|
<div class="flex min-w-0 flex-grow flex-col gap-1">
|
||||||
<div class="flex flex-grow justify-between gap-2">
|
<CalendarEventHeader event={$event} />
|
||||||
<CalendarEventHeader event={$event} />
|
<CalendarEventMeta event={$event} />
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2 text-sm opacity-75">
|
|
||||||
<CalendarEventMeta event={$event} />
|
|
||||||
</div>
|
|
||||||
<div class="flex py-2 opacity-50">
|
<div class="flex py-2 opacity-50">
|
||||||
<div class="h-px flex-grow bg-base-content opacity-25"></div>
|
<div class="h-px flex-grow bg-base-content opacity-25"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user