Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discuss UI: Wire up existing event handlers #129

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1832659
Trigger minor diff
rtshkmr Oct 9, 2024
e264c33
Delete marks from read mode (#117)
rtshkmr Oct 12, 2024
d21f545
[BUGFIX] Explicitly pipe apply_action, not helm
Oct 14, 2024
258f4d3
Edit marks in read mode (#118)
rtshkmr Oct 17, 2024
6b16b92
Add a generic modal wrapper (#120)
rtshkmr Oct 17, 2024
007e0a3
Sheaf CRUD: Add UI skeleton, data plumbing for creating sheafs (#121)
rtshkmr Oct 26, 2024
a8c1921
Add helpers to generate a family of sheafs for testing disc mode (#122)
rtshkmr Oct 26, 2024
7f8f07d
Add data, ui lattices and ui primitives for the discuss context (#124)
rtshkmr Oct 26, 2024
858ae27
Refactor: read mode keeps sheaf state only (#125)
rtshkmr Oct 26, 2024
400ad53
Read mode: reply_to relies on draft sheaf's parent (#127)
rtshkmr Oct 26, 2024
cf8e00a
force diffs
rtshkmr Oct 26, 2024
2a3d465
Simplify read::init_reply_to_context
rtshkmr Oct 26, 2024
9c39948
Add init routines for draft & replyto
rtshkmr Oct 26, 2024
75f0c8d
Add ui stubs for sheaf engagement buttons
rtshkmr Oct 26, 2024
965a26e
Cleanup the attrs accepted by curr components
rtshkmr Oct 26, 2024
cabb226
Inject children into sheaf summary
rtshkmr Oct 26, 2024
9da9eb6
the holy brace (#130)
ks0m1c Oct 27, 2024
5b6d315
Discuss: init drafting and reply_to contexts (#128)
rtshkmr Oct 27, 2024
5f7d5e9
Merge branch 'epic/discussions' into feat/context-gate/discuss-ui-sta…
rtshkmr Oct 27, 2024
7dced6d
Handle toggling of collapsible_sheaf_container
rtshkmr Oct 27, 2024
a1bb2b8
Pass sheaf(_ui) as props instead of marks(_ui)
rtshkmr Oct 27, 2024
1b4577d
Support ui::toggle_marks_display_collapsibility
rtshkmr Oct 27, 2024
97e2c8d
marks: ui:toggle, and editing in published sheafs
rtshkmr Oct 28, 2024
0061b47
Modal toggle for quick reply
rtshkmr Oct 28, 2024
063b5ba
Add focus pin toggle
rtshkmr Oct 29, 2024
5ab1b36
Add example for navigate::visit_mark
rtshkmr Oct 29, 2024
453314b
vanity: Add reddit-sheaftop collapse toggle
rtshkmr Oct 29, 2024
c812a6c
Add sheaf::share_sheaf icon and empty handler
rtshkmr Oct 29, 2024
6d74e7a
Cleanup to improve the review process
rtshkmr Oct 30, 2024
afabc77
bind, share and jump around (#132)
ks0m1c Nov 11, 2024
1a68e46
Merge branch 'epic/discussions' into feat/context-gate/discuss-ui-sta…
rtshkmr Nov 14, 2024
9f9aa6c
Styling changes to highlighted span
rtshkmr Nov 14, 2024
c98aaa2
Add mode-specific routes, inject mode @ mediator (#133)
rtshkmr Nov 15, 2024
487b0c9
Fix parsing error when extracting injected mode (#134)
rtshkmr Nov 16, 2024
937eb68
Disciple Panels (#137)
ks0m1c Dec 8, 2024
3079b5b
yMerge branch 'epic/discussions' into feat/context-gate/discuss-ui-st…
Dec 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.org
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ Wherever there be anything you dost not comprehend, cease to continue writing
-- Vyasa, Adi Parva - Mahabharatam
#+END_QUOTE


* What is the _*Vyasa Project*_?
=TODO=

Expand Down
96 changes: 64 additions & 32 deletions assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,24 @@
**/
@layer base {
:root {
--chili-red: #E83A21ff;
--pearl: #E3DABEff;
--chili-red: #e83a21ff;
--pearl: #e3dabeff;
--black: #070302ff;
--dun: #D4CAABff;
--blood-red: #5B1208ff;
--dun: #deb587;
--blood-red: #5b1208ff;

--white-alabaster: #F3EFE3;
--linen: #FAF1E6;

--aerospace-orange: #FD4F00;
--white-alabaster: #f3efe3;
--linen: #faf1e6;
--deep-saffron: #ff9933;
--coral-orange: #FF8349;
--atomic-tangerine: #FF9B6D;


--brown: #922E00;
--sienna: #7F2800;
--rust: #AE3700;
--brown: #922e00;
--sienna: #7f2800;
--rust: #ae3700;

--color-brand: var(--aerospace-orange);
--color-brand: var(--deep-saffron);
--color-brand-light: var(--atomic-tangerine);
--color-brand-extra-light: var(--linen);
--color-brand-accent: var(--aerospace-orange);
Expand All @@ -40,50 +39,59 @@
--color-brand-dark: var(--brown);
--color-brand-extra-dark: var(--sienna);


--color-primary: var(--chili-red);
--color-primary-accent: var(--chili-red);
--color-primary-background: var(--white-alabaster);
--color-secondary: var(--blood-red);
--color-secondary-background: var(--dun);
--color-text: var(--black)
--color-text: var(--black);
}
}

/* fonts declared from priv/assets/fonts */

@font-face {
font-family: "Karm";
src: url('/fonts/dn/Karm-Medium.woff2') format('woff2'),
url('/fonts/dn/Karm-Medium.woff') format('woff'),
url('/fonts/dn/Karm-Medium.ttf') format('ttf');
font-family: "Karm";
src:
url("/fonts/dn/Karm-Medium.woff2") format("woff2"),
url("/fonts/dn/Karm-Medium.woff") format("woff"),
url("/fonts/dn/Karm-Medium.ttf") format("ttf");
}

@font-face {
font-family: "Vyas";
src: url('/fonts/ta/VyasaTA.woff2') format('woff2'),
url('/fonts/ta/VyasaTA.woff') format('woff'),
url('/fonts/ta/VyasaTA.ttf') format('ttf');
font-family: "Vyas";
src:
url("/fonts/ta/VyasaTA.woff2") format("woff2"),
url("/fonts/ta/VyasaTA.woff") format("woff"),
url("/fonts/ta/VyasaTA.ttf") format("ttf");
}

@font-face {
font-family: "Vyas";
src: url('/fonts/ta/VyasaTA.woff2') format('woff2'),
url('/fonts/ta/VyasaTA.woff') format('woff'),
url('/fonts/ta/VyasaTA.ttf') format('ttf');
font-family: "Vyas";
src:
url("/fonts/ta/VyasaTA.woff2") format("woff2"),
url("/fonts/ta/VyasaTA.woff") format("woff"),
url("/fonts/ta/VyasaTA.ttf") format("ttf");
}


/* This file is for your main application CSS */

.emphasized-verse {
@apply bg-brandAccentLight border-b-0 border-l-8 border-black p-4 pl-8 rounded-sm;
@apply bg-brandAccentLight border-b-0 border-l-8 border-black p-4 pl-8 rounded-sm;
}

@font-face {
font-family: "et-book";
src: url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot");
src: url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.woff") format("woff"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.ttf") format("truetype"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.svg#etbookromanosf") format("svg");
src:
url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot?#iefix")
format("embedded-opentype"),
url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.woff")
format("woff"),
url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.ttf")
format("truetype"),
url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.svg#etbookromanosf")
format("svg");
font-weight: normal;
font-style: normal;
font-display: swap;
Expand All @@ -92,7 +100,15 @@
@font-face {
font-family: "et-book";
src: url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot");
src: url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.woff") format("woff"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.ttf") format("truetype"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.svg#etbookromanosf") format("svg");
src:
url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot?#iefix")
format("embedded-opentype"),
url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.woff")
format("woff"),
url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.ttf")
format("truetype"),
url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.svg#etbookromanosf")
format("svg");
font-weight: normal;
font-style: italic;
font-display: swap;
Expand All @@ -101,7 +117,15 @@
@font-face {
font-family: "et-book";
src: url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot");
src: url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.woff") format("woff"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.ttf") format("truetype"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.svg#etbookromanosf") format("svg");
src:
url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot?#iefix")
format("embedded-opentype"),
url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.woff")
format("woff"),
url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.ttf")
format("truetype"),
url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.svg#etbookromanosf")
format("svg");
font-weight: bold;
font-style: normal;
font-display: swap;
Expand All @@ -110,7 +134,15 @@
@font-face {
font-family: "et-book-roman-old-style";
src: url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot");
src: url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.woff") format("woff"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.ttf") format("truetype"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.svg#etbookromanosf") format("svg");
src:
url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot?#iefix")
format("embedded-opentype"),
url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.woff")
format("woff"),
url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.ttf")
format("truetype"),
url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.svg#etbookromanosf")
format("svg");
font-weight: normal;
font-style: normal;
font-display: swap;
Expand Down
46 changes: 32 additions & 14 deletions assets/js/hooks/hoverune.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,18 @@ function floatHoveRune({ clientX, clientY }) {
top: `${y}px`,
});
});

// computePosition(virtualEl, hoverune, {placement: 'top-end', middleware: [inline(getSelectRect.x, getSelectRect.y), offset(5)]}).then(({x, y}) => {
// hoverune.classList.remove("hidden")
// Object.assign(hoverune.style, {
// left: `${getSelectRect.x}px`,
// top: `${y}px`,
// });
// })
}

const findMatchingSpan = ({ node_id, field }) => {
// Early return if missing either criteria
if (!node_id || !field) return null;

// Find first span with both exact matches
return document.querySelector(
`span[node_id="${node_id}"][field="${field}"]`
);
};

const findHook = (el) => findParent(el, "phx-hook", "HoveRune");
const findMarginote = (el) => findParent(el, "phx-hook", "MargiNote");
const findNode = (el) => el && el.getAttribute("node");
Expand All @@ -62,36 +64,52 @@ export default HoveRune = {
console.log("CHECK HOVERUNE", {
dset: this.el.dataset,
});
const targetEvents = ["pointerdown", "pointerup"];

this.handleEvent("bind::jump", (bind) => {
console.warn(bind)
targetNode = findMatchingSpan(bind)
if (targetNode) {
targetNode.focus();
targetNode.scrollIntoView({
behavior: "smooth",
block: "center",
});
}

});
const targetEvents = ["pointerdown", "pointerup"];
targetEvents.forEach((e) =>
window.addEventListener(e, ({ target }) => {
var selection = window.getSelection();
if (!selection || selection.rangeCount <= 0) {
return;
}

var getSelectRect = selection.getRangeAt(0).getBoundingClientRect();
var range = selection.getRangeAt(0);
var getSelectRect = range.getBoundingClientRect();
const getSelectText = selection.toString();
//const validElem = findHook(target)
// const isMarginote = findMarginote(target)
const isNode = findNode(target);

console.log("binding selected here!")
console.log(selection)

if (isNode) {
binding = forgeBinding(target, [
"node",
"node_id",
"text",
"field",
"verse_id",
]);
binding["selection"] = getSelectText;

console.log("CHECK HOVERUNE", {
eventTarget: this.eventTarget,
target: `#${this.eventTarget}`,
payload: { binding: binding },
});
this.pushEventTo(`#${this.eventTarget}`, "bindHoveRune", {
this.pushEvent("bind::to", {
binding: binding,
target: this.eventTarget
});

console.log(binding);
Expand Down
4 changes: 4 additions & 0 deletions assets/js/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import HoveRune from "./hoverune.js";
import Scrolling from "./scrolling.js";
import ButtonClickRelayer from "./button_click_relayer.js";
import SessionBox from "./session_box.js";
import TextareaAutoResize from "./textarea_auto_resize.js";
import PseudoForm from "./pseudo_form.js";

let Hooks = {
ShareQuoteButton,
Expand All @@ -26,6 +28,8 @@ let Hooks = {
Scrolling,
ButtonClickRelayer,
SessionBox,
TextareaAutoResize,
PseudoForm,
};

export default Hooks;
103 changes: 103 additions & 0 deletions assets/js/hooks/pseudo_form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* {PseudoForm}
*
* This hook facilitates the capture of input values from "form elements" without resorting to
* tradition form-submission mechanisms. This approach is useful to avoid the nested form problem -- wherein
* nested forms lead to unintended behaviour because a submission event in a child form will get bubbled out (propagated)
* to the parent, as per HTML-DOM spec and will cause the parent's submit to also be triggerred. This would mean that
* without the user desiring it so, there will be a form submission. While there are ways to hack this, for example by
* preventing the bubble propagation from happening, those solutions are not long-term, which brings us to
* consider this hook as an alternative approach. By using this hook, we can effectively manage input values while keeping
* components stateless and avoiding the complexities associated with nested forms.

* **Why Nested Forms Are an Antipattern**: [perplexity-generated section]
* - **Event Propagation Issues**: Nested forms can cause submit events to bubble up, leading to unintended submissions of parent forms.
* - **Complexity**: Managing state and events across nested forms increases the complexity of your application, making it harder to maintain.
* - **Accessibility Concerns**: Screen readers and assistive technologies may struggle with nested forms, potentially leading to a poor user experience.
* - **HTML Specification**: The HTML specification does not support nested forms, which can lead to inconsistent behavior across different browsers.
*
* **Value of Stateless Function Components**: [perplexity-generated section]
* - Using function components that do not rely on form state allows for greater composability and flexibility.
* - It enables nesting of components without the constraints imposed by traditional form handling, allowing for cleaner and more maintainable code.
* - This approach promotes reusability and modularity within your codebase, making it easier to build complex UIs without the overhead of managing form state.
*
* Overall Mechanism for this hook:
* - The hook listens for a specified event on the element it is attached to.
* - When the event is triggered, it captures the value of a designated input element.
* - It then pushes an event to the LiveView server with the captured input value and any additional payload.
*
* Expected Dataset Parameters:
* - `data-event-to-capture`: A string representing the type of event to listen for (e.g., "click").
* This allows flexibility in determining which user interaction will trigger the value capture.
*
* - `data-target-selector`: A CSS selector string that identifies the input element from which to capture
* the value. This makes the hook generic and reusable for different types of inputs (e.g., textareas,
* text inputs).
*
* - `data-event-name`: A string representing the name of the event to be pushed to the LiveView server.
* If not provided, it defaults to "submitPseudoForm".
*
* - `data-event-target`: A string representing the target for the event push. This should match the
* target LiveView or component that will handle the event on the server side.
*
* - `data-event-payload`: A JSON string representing any additional data you want to send along with
* the captured input value. This allows for more complex interactions without needing to modify
* component state externally. This is actually a static part of the eventual payload that the event needs to have, we shall merge this with the input that we will be reading.
*
* Example Usage:
* <button
* phx-hook="PseudoForm"
* data-event-to-capture="click"
* data-target-selector="#input-id"
* data-event-name="mark::editMarkContent"
* data-event-target="targetLiveView"
* data-event-payload='{"additional_key": "additional_value"}'
* >
* Submit
* </button>
*
* NOTE: this currently only handles single-input fields. This hook may be extended to
* handle a group of input fields following a similar approach; otherwise, we should fallback to
* typical LiveView idioms that deal with how to handle forms.
*/

PseudoForm = {
mounted() {
const eventToCapture = this.el.getAttribute("data-event-to-capture");
this.el.addEventListener(eventToCapture, (_event) => {
const targetSelector = this.el.getAttribute("data-target-selector");
const eventName =
this.el.getAttribute("data-event-name") || "submitPseudoForm";
const eventPayload = JSON.parse(
this.el.getAttribute("data-event-payload") || "{}",
);
const eventTarget = this.el.getAttribute("data-event-target");
const inputElement = document.querySelector(targetSelector);

if (inputElement) {
const value = inputElement.value; // Reads the current value of the input
const finalPayload = {
...eventPayload,
input: value.trim(),
};
console.info("Using a pseudoform", {
eventName,
eventTarget,
eventPayload,
value,
finalPayload,
});
this.pushEventTo(eventTarget, eventName, finalPayload);
} else {
console.warn("Desired input element not found, params:", {
targetSelector,
eventName,
eventTarget,
eventPayload,
});
}
});
},
};

export default PseudoForm;
12 changes: 12 additions & 0 deletions assets/js/hooks/session_box.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
SessionBox = {
mounted() {
this.handleEvent("initSession", (sess) => this.initSession(sess));

this.handleEvent("session::share", (bind) => {
if ("share" in navigator) {
// uses webshare api:
window.shareUrl(bind.url);
} else if ("clipboard" in navigator) {
navigator.clipboard.writeText(bind.url);
} else {
alert("Here is the url to share: #{bind.url}");
}

});
},

initSession(sess) {
Expand Down
Loading