diff --git a/mixins/Postable.pixy b/mixins/Postable.pixy index baf09b73..4ed21abe 100644 --- a/mixins/Postable.pixy +++ b/mixins/Postable.pixy @@ -1,5 +1,5 @@ -component Postable(post arn.Postable, highlightAuthorID string) - .post.mountable(id=post.ID(), data-highlight=post.Author().ID == highlightAuthorID) +component Postable(post arn.Postable, user *arn.User, highlightAuthorID string) + .post.mountable(id=strings.ToLower(post.Type()) + "-" + toString(post.ID()), data-highlight=post.Author().ID == highlightAuthorID, data-api="/api/" + strings.ToLower(post.Type()) + "/" + post.ID()) .post-author Avatar(post.Author()) @@ -9,34 +9,38 @@ component Postable(post arn.Postable, highlightAuthorID string) .post-content div(id="render-" + post.ID())!= post.HTML() - //- if user && user.ID === post.authorId - //- textarea.post-input.hidden(id="source-" + post.ID)= post.text - //- a.post-save.hidden(id="save-" + post.ID, onclick=`$.saveEdit("${type.toLowerCase()}", "${post.ID}")`) - //- i.fa.fa-save - //- span Save + if user != nil && user.ID == post.Author().ID + textarea.post-input.hidden(id="source-" + post.ID())= post.Text() + .buttons.hidden(id="edit-toolbar-" + post.ID()) + a.button.post-save.action(data-action="savePost", data-trigger="click", data-id=post.ID()) + Icon("save") + span Save + + a.button.post-cancel-edit.action(data-action="editPost", data-trigger="click", data-id=post.ID()) + Icon("close") + span Cancel .post-toolbar(id="toolbar-" + post.ID()) .spacer .post-likes(id="likes-" + post.ID(), title="Likes")= len(post.Likes()) - //- if user != nil - //- if user.ID !== post.authorId - //- - var liked = post.likes && post.likes.indexOf(user.ID) !== -1 + if user != nil + //- if user.ID !== post.authorId + //- - var liked = post.likes && post.likes.indexOf(user.ID) !== -1 - //- a.post-tool.post-like(id="like-" + post.ID, onclick=`$.like("${type.toLowerCase()}", "${post.ID}")`, title="Like", class=liked ? "hidden" : ") - //- i.fa.fa-thumbs-up.fa-fw + //- a.post-tool.post-like(id="like-" + post.ID, onclick=`$.like("${type.toLowerCase()}", "${post.ID}")`, title="Like", class=liked ? "hidden" : ") + //- i.fa.fa-thumbs-up.fa-fw - //- a.post-tool.post-unlike(id="unlike-" + post.ID, onclick=`$.unlike("${type.toLowerCase()}", "${post.ID}")`, title="Unlike", class=!liked ? "hidden" : ") - //- i.fa.fa-thumbs-down.fa-fw + //- a.post-tool.post-unlike(id="unlike-" + post.ID, onclick=`$.unlike("${type.toLowerCase()}", "${post.ID}")`, title="Unlike", class=!liked ? "hidden" : ") + //- i.fa.fa-thumbs-down.fa-fw - //- if type === "Posts" || type === "Threads" - //- if user.ID === post.authorId - //- a.post-tool.post-edit(onclick=`$.edit("${post.ID}")`, title="Edit") - //- i.fa.fa-pencil.fa-fw + if user.ID == post.Author().ID + a.post-tool.post-edit.action(data-action="editPost", data-trigger="click", data-id=post.ID(), title="Edit") + RawIcon("pencil") if post.Type() != "Thread" a.post-tool.post-permalink.ajax(href=post.Link(), title="Permalink") - Icon("link") + RawIcon("link") //- if type === "Messages" && user && (user.ID === post.authorId || user.ID === post.recipientId) //- a.post-tool.post-delete(onclick=`if(confirm("Do you really want to delete this ${typeSingular.toLowerCase()} from ${post.author.nick}?")) $.delete${typeSingular}("${post.ID}")`, title="Delete") diff --git a/mixins/PostableList.pixy b/mixins/PostableList.pixy index c2f956a9..bd31d475 100644 --- a/mixins/PostableList.pixy +++ b/mixins/PostableList.pixy @@ -1,5 +1,5 @@ -component PostableList(postables []arn.Postable) +component PostableList(postables []arn.Postable, user *arn.User) .thread .posts each post in postables - Postable(post, "") + Postable(post, user, "") diff --git a/pages/posts/posts.go b/pages/posts/posts.go index 5e705172..b6bf6aca 100644 --- a/pages/posts/posts.go +++ b/pages/posts/posts.go @@ -6,16 +6,18 @@ import ( "github.com/aerogo/aero" "github.com/animenotifier/arn" "github.com/animenotifier/notify.moe/components" + "github.com/animenotifier/notify.moe/utils" ) // Get post. func Get(ctx *aero.Context) string { id := ctx.Get("id") + user := utils.GetUser(ctx) post, err := arn.GetPost(id) if err != nil { return ctx.Error(http.StatusNotFound, "Post not found", err) } - return ctx.HTML(components.Post(post)) + return ctx.HTML(components.Post(post, user)) } diff --git a/pages/posts/posts.pixy b/pages/posts/posts.pixy index 6d02e8cc..2de3e8b1 100644 --- a/pages/posts/posts.pixy +++ b/pages/posts/posts.pixy @@ -1,5 +1,5 @@ -component Post(post *arn.Post) - Postable(post.ToPostable(), "") +component Post(post *arn.Post, user *arn.User) + Postable(post.ToPostable(), user, "") .side-note a.ajax(href=post.Thread().Link())= post.Thread().Title diff --git a/pages/profile/posts.pixy b/pages/profile/posts.pixy index 6db5da81..981c336a 100644 --- a/pages/profile/posts.pixy +++ b/pages/profile/posts.pixy @@ -3,6 +3,6 @@ component LatestPosts(postables []arn.Postable, viewUser *arn.User, user *arn.Us if len(postables) > 0 h2.page-title= len(postables), " latest posts by ", postables[0].Author().Nick - PostableList(postables) + PostableList(postables, user) else p.no-data.mountable= viewUser.Nick + " hasn't written any posts yet." \ No newline at end of file diff --git a/pages/threads/threads.pixy b/pages/threads/threads.pixy index 9a1014bb..77828501 100644 --- a/pages/threads/threads.pixy +++ b/pages/threads/threads.pixy @@ -3,10 +3,10 @@ component Thread(thread *arn.Thread, posts []*arn.Post, user *arn.User) #thread.thread(data-id=thread.ID) .posts - Postable(thread.ToPostable(), thread.Author().ID) + Postable(thread.ToPostable(), user, thread.Author().ID) each post in posts - Postable(post.ToPostable(), thread.Author().ID) + Postable(post.ToPostable(), user, thread.Author().ID) // Reply if user != nil diff --git a/rewrite.go b/rewrite.go index c2ed4819..edc0a7be 100644 --- a/rewrite.go +++ b/rewrite.go @@ -43,7 +43,7 @@ func init() { } if requestURI == "/dark-flame-master" { - ctx.SetURI("/api/analytics/new") + ctx.SetURI("/api/new/analytics") return } }) diff --git a/scripts/Actions.ts b/scripts/Actions.ts index b5aafabc..65cb5d43 100644 --- a/scripts/Actions.ts +++ b/scripts/Actions.ts @@ -1,6 +1,7 @@ import { Application } from "./Application" import { AnimeNotifier } from "./AnimeNotifier" import { Diff } from "./Diff" +import { findAll } from "./Utils" // Save new data from an input field export function save(arn: AnimeNotifier, input: HTMLInputElement | HTMLTextAreaElement) { @@ -20,29 +21,15 @@ export function save(arn: AnimeNotifier, input: HTMLInputElement | HTMLTextAreaE obj[input.dataset.field] = value } - // console.log(input.type, input.dataset.api, obj, JSON.stringify(obj)) - - let apiObject: HTMLElement - let parent = input as HTMLElement - - while(parent = parent.parentElement) { - if(parent.dataset.api !== undefined) { - apiObject = parent - break - } - } - - if(!apiObject) { - throw "API object not found" - } - if(isContentEditable) { input.contentEditable = "false" } else { input.disabled = true } - fetch(apiObject.dataset.api, { + let apiEndpoint = arn.findAPIEndpoint(input) + + fetch(apiEndpoint, { method: "POST", body: JSON.stringify(obj), credentials: "same-origin" @@ -82,40 +69,46 @@ export function soon() { export function diff(arn: AnimeNotifier, element: HTMLElement) { let url = element.dataset.url || (element as HTMLAnchorElement).getAttribute("href") - arn.diff(url).then(() => { - const duration = 250.0 - const steps = 60 - const interval = duration / steps - const fullSin = Math.PI / 2 - const contentPadding = 24 - - let target = element - let scrollHandle: number - let oldScroll = arn.app.content.parentElement.scrollTop - let newScroll = 0 - let finalScroll = Math.max(target.offsetTop - contentPadding, 0) - let scrollDistance = finalScroll - oldScroll - let timeStart = Date.now() - let timeEnd = timeStart + duration + arn.diff(url).then(() => arn.scrollTo(element)) +} - let scroll = () => { - let time = Date.now() - let progress = (time - timeStart) / duration +// Edit post +export function editPost(arn: AnimeNotifier, element: HTMLElement) { + let postId = element.dataset.id - if(progress > 1.0) { - progress = 1.0 - } + let render = arn.app.find("render-" + postId) + let toolbar = arn.app.find("toolbar-" + postId) + let source = arn.app.find("source-" + postId) + let edit = arn.app.find("edit-toolbar-" + postId) - newScroll = oldScroll + scrollDistance * Math.sin(progress * fullSin) - arn.app.content.parentElement.scrollTop = newScroll + if(!render.classList.contains("hidden")) { + render.classList.add("hidden") + toolbar.classList.add("hidden") + source.classList.remove("hidden") + edit.classList.remove("hidden") + } else { + render.classList.remove("hidden") + toolbar.classList.remove("hidden") + source.classList.add("hidden") + edit.classList.add("hidden") + } +} - if(time < timeEnd && newScroll != finalScroll) { - window.requestAnimationFrame(scroll) - } - } +// Save post +export function savePost(arn: AnimeNotifier, element: HTMLElement) { + let postId = element.dataset.id + let source = arn.app.find("source-" + postId) as HTMLTextAreaElement + let text = source.value - window.requestAnimationFrame(scroll) - }) + let updates = { + Text: text, + } + + let apiEndpoint = arn.findAPIEndpoint(element) + + arn.post(apiEndpoint, updates) + .then(() => arn.reloadContent()) + .catch(console.error) } // Forum reply @@ -129,7 +122,7 @@ export function forumReply(arn: AnimeNotifier) { tags: [] } - arn.post("/api/post/new", post) + arn.post("/api/new/post", post) .then(() => arn.reloadContent()) .then(() => textarea.value = "") .catch(console.error) @@ -147,7 +140,7 @@ export function createThread(arn: AnimeNotifier) { tags: [category.value] } - arn.post("/api/thread/new", thread) + arn.post("/api/new/thread", thread) .then(() => arn.app.load("/forum/" + thread.tags[0])) .catch(console.error) } @@ -168,7 +161,7 @@ export function createSoundTrack(arn: AnimeNotifier, button: HTMLButtonElement) button.innerText = "Adding..." button.disabled = true - arn.post("/api/soundtrack/new", soundtrack) + arn.post("/api/new/soundtrack", soundtrack) .then(() => arn.app.load("/music")) .catch(err => { console.error(err) diff --git a/scripts/AnimeNotifier.ts b/scripts/AnimeNotifier.ts index 57fe38ea..e293f81d 100644 --- a/scripts/AnimeNotifier.ts +++ b/scripts/AnimeNotifier.ts @@ -321,6 +321,58 @@ export class AnimeNotifier { }) } + scrollTo(target: HTMLElement) { + const duration = 250.0 + const steps = 60 + const interval = duration / steps + const fullSin = Math.PI / 2 + const contentPadding = 24 + + let scrollHandle: number + let oldScroll = this.app.content.parentElement.scrollTop + let newScroll = 0 + let finalScroll = Math.max(target.offsetTop - contentPadding, 0) + let scrollDistance = finalScroll - oldScroll + let timeStart = Date.now() + let timeEnd = timeStart + duration + + let scroll = () => { + let time = Date.now() + let progress = (time - timeStart) / duration + + if(progress > 1.0) { + progress = 1.0 + } + + newScroll = oldScroll + scrollDistance * Math.sin(progress * fullSin) + this.app.content.parentElement.scrollTop = newScroll + + if(time < timeEnd && newScroll != finalScroll) { + window.requestAnimationFrame(scroll) + } + } + + window.requestAnimationFrame(scroll) + } + + findAPIEndpoint(element: HTMLElement) { + let apiObject: HTMLElement + let parent = element + + while(parent = parent.parentElement) { + if(parent.dataset.api !== undefined) { + apiObject = parent + break + } + } + + if(!apiObject) { + throw "API object not found" + } + + return apiObject.dataset.api + } + onPopState(e: PopStateEvent) { if(e.state) { this.app.load(e.state, { diff --git a/styles/base.scarlet b/styles/base.scarlet index 3421b8bd..6892df79 100644 --- a/styles/base.scarlet +++ b/styles/base.scarlet @@ -32,5 +32,8 @@ a img backface-visibility hidden +.hidden + display none !important + .spacer flex 1 \ No newline at end of file diff --git a/styles/forum.scarlet b/styles/forum.scarlet index 297a325e..17c2d107 100644 --- a/styles/forum.scarlet +++ b/styles/forum.scarlet @@ -105,6 +105,12 @@ .post-unlike color rgb(255, 32, 12) !important +.post-save + // + +.post-input + min-height 200px + // Old // #posts diff --git a/styles/input.scarlet b/styles/input.scarlet index 9b0702d8..8356881d 100644 --- a/styles/input.scarlet +++ b/styles/input.scarlet @@ -1,6 +1,5 @@ mixin input-focus :focus - color black border 1px solid input-focus-border-color // TODO: Replace with alpha(main-color, 20%) function box-shadow 0 0 6px rgba(248, 165, 130, 0.2) diff --git a/tests.go b/tests.go index 086b95e8..f26ca750 100644 --- a/tests.go +++ b/tests.go @@ -201,9 +201,11 @@ var interfaceImplementations = map[string][]reflect.Type{ }, "Thread": []reflect.Type{ creatable, + updatable, }, "Post": []reflect.Type{ creatable, + updatable, }, "SoundTrack": []reflect.Type{ creatable,