Added forum post editing
This commit is contained in:
parent
9de86a83f9
commit
5070600964
@ -1,5 +1,5 @@
|
|||||||
component Postable(post arn.Postable, highlightAuthorID string)
|
component Postable(post arn.Postable, user *arn.User, highlightAuthorID string)
|
||||||
.post.mountable(id=post.ID(), data-highlight=post.Author().ID == highlightAuthorID)
|
.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
|
.post-author
|
||||||
Avatar(post.Author())
|
Avatar(post.Author())
|
||||||
|
|
||||||
@ -9,34 +9,38 @@ component Postable(post arn.Postable, highlightAuthorID string)
|
|||||||
.post-content
|
.post-content
|
||||||
div(id="render-" + post.ID())!= post.HTML()
|
div(id="render-" + post.ID())!= post.HTML()
|
||||||
|
|
||||||
//- if user && user.ID === post.authorId
|
if user != nil && user.ID == post.Author().ID
|
||||||
//- textarea.post-input.hidden(id="source-" + post.ID)= post.text
|
textarea.post-input.hidden(id="source-" + post.ID())= post.Text()
|
||||||
//- a.post-save.hidden(id="save-" + post.ID, onclick=`$.saveEdit("${type.toLowerCase()}", "${post.ID}")`)
|
.buttons.hidden(id="edit-toolbar-" + post.ID())
|
||||||
//- i.fa.fa-save
|
a.button.post-save.action(data-action="savePost", data-trigger="click", data-id=post.ID())
|
||||||
//- span Save
|
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())
|
.post-toolbar(id="toolbar-" + post.ID())
|
||||||
.spacer
|
.spacer
|
||||||
.post-likes(id="likes-" + post.ID(), title="Likes")= len(post.Likes())
|
.post-likes(id="likes-" + post.ID(), title="Likes")= len(post.Likes())
|
||||||
|
|
||||||
//- if user != nil
|
if user != nil
|
||||||
//- if user.ID !== post.authorId
|
//- if user.ID !== post.authorId
|
||||||
//- - var liked = post.likes && post.likes.indexOf(user.ID) !== -1
|
//- - 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" : ")
|
//- 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
|
//- 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" : ")
|
//- 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
|
//- i.fa.fa-thumbs-down.fa-fw
|
||||||
|
|
||||||
//- if type === "Posts" || type === "Threads"
|
if user.ID == post.Author().ID
|
||||||
//- if user.ID === post.authorId
|
a.post-tool.post-edit.action(data-action="editPost", data-trigger="click", data-id=post.ID(), title="Edit")
|
||||||
//- a.post-tool.post-edit(onclick=`$.edit("${post.ID}")`, title="Edit")
|
RawIcon("pencil")
|
||||||
//- i.fa.fa-pencil.fa-fw
|
|
||||||
|
|
||||||
if post.Type() != "Thread"
|
if post.Type() != "Thread"
|
||||||
a.post-tool.post-permalink.ajax(href=post.Link(), title="Permalink")
|
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)
|
//- 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")
|
//- 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")
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
component PostableList(postables []arn.Postable)
|
component PostableList(postables []arn.Postable, user *arn.User)
|
||||||
.thread
|
.thread
|
||||||
.posts
|
.posts
|
||||||
each post in postables
|
each post in postables
|
||||||
Postable(post, "")
|
Postable(post, user, "")
|
||||||
|
@ -6,16 +6,18 @@ import (
|
|||||||
"github.com/aerogo/aero"
|
"github.com/aerogo/aero"
|
||||||
"github.com/animenotifier/arn"
|
"github.com/animenotifier/arn"
|
||||||
"github.com/animenotifier/notify.moe/components"
|
"github.com/animenotifier/notify.moe/components"
|
||||||
|
"github.com/animenotifier/notify.moe/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get post.
|
// Get post.
|
||||||
func Get(ctx *aero.Context) string {
|
func Get(ctx *aero.Context) string {
|
||||||
id := ctx.Get("id")
|
id := ctx.Get("id")
|
||||||
|
user := utils.GetUser(ctx)
|
||||||
post, err := arn.GetPost(id)
|
post, err := arn.GetPost(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx.Error(http.StatusNotFound, "Post not found", err)
|
return ctx.Error(http.StatusNotFound, "Post not found", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.HTML(components.Post(post))
|
return ctx.HTML(components.Post(post, user))
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
component Post(post *arn.Post)
|
component Post(post *arn.Post, user *arn.User)
|
||||||
Postable(post.ToPostable(), "")
|
Postable(post.ToPostable(), user, "")
|
||||||
|
|
||||||
.side-note
|
.side-note
|
||||||
a.ajax(href=post.Thread().Link())= post.Thread().Title
|
a.ajax(href=post.Thread().Link())= post.Thread().Title
|
||||||
|
@ -3,6 +3,6 @@ component LatestPosts(postables []arn.Postable, viewUser *arn.User, user *arn.Us
|
|||||||
|
|
||||||
if len(postables) > 0
|
if len(postables) > 0
|
||||||
h2.page-title= len(postables), " latest posts by ", postables[0].Author().Nick
|
h2.page-title= len(postables), " latest posts by ", postables[0].Author().Nick
|
||||||
PostableList(postables)
|
PostableList(postables, user)
|
||||||
else
|
else
|
||||||
p.no-data.mountable= viewUser.Nick + " hasn't written any posts yet."
|
p.no-data.mountable= viewUser.Nick + " hasn't written any posts yet."
|
@ -3,10 +3,10 @@ component Thread(thread *arn.Thread, posts []*arn.Post, user *arn.User)
|
|||||||
|
|
||||||
#thread.thread(data-id=thread.ID)
|
#thread.thread(data-id=thread.ID)
|
||||||
.posts
|
.posts
|
||||||
Postable(thread.ToPostable(), thread.Author().ID)
|
Postable(thread.ToPostable(), user, thread.Author().ID)
|
||||||
|
|
||||||
each post in posts
|
each post in posts
|
||||||
Postable(post.ToPostable(), thread.Author().ID)
|
Postable(post.ToPostable(), user, thread.Author().ID)
|
||||||
|
|
||||||
// Reply
|
// Reply
|
||||||
if user != nil
|
if user != nil
|
||||||
|
@ -43,7 +43,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if requestURI == "/dark-flame-master" {
|
if requestURI == "/dark-flame-master" {
|
||||||
ctx.SetURI("/api/analytics/new")
|
ctx.SetURI("/api/new/analytics")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Application } from "./Application"
|
import { Application } from "./Application"
|
||||||
import { AnimeNotifier } from "./AnimeNotifier"
|
import { AnimeNotifier } from "./AnimeNotifier"
|
||||||
import { Diff } from "./Diff"
|
import { Diff } from "./Diff"
|
||||||
|
import { findAll } from "./Utils"
|
||||||
|
|
||||||
// Save new data from an input field
|
// Save new data from an input field
|
||||||
export function save(arn: AnimeNotifier, input: HTMLInputElement | HTMLTextAreaElement) {
|
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
|
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) {
|
if(isContentEditable) {
|
||||||
input.contentEditable = "false"
|
input.contentEditable = "false"
|
||||||
} else {
|
} else {
|
||||||
input.disabled = true
|
input.disabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(apiObject.dataset.api, {
|
let apiEndpoint = arn.findAPIEndpoint(input)
|
||||||
|
|
||||||
|
fetch(apiEndpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(obj),
|
body: JSON.stringify(obj),
|
||||||
credentials: "same-origin"
|
credentials: "same-origin"
|
||||||
@ -82,40 +69,46 @@ export function soon() {
|
|||||||
export function diff(arn: AnimeNotifier, element: HTMLElement) {
|
export function diff(arn: AnimeNotifier, element: HTMLElement) {
|
||||||
let url = element.dataset.url || (element as HTMLAnchorElement).getAttribute("href")
|
let url = element.dataset.url || (element as HTMLAnchorElement).getAttribute("href")
|
||||||
|
|
||||||
arn.diff(url).then(() => {
|
arn.diff(url).then(() => arn.scrollTo(element))
|
||||||
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
|
|
||||||
|
|
||||||
let scroll = () => {
|
// Edit post
|
||||||
let time = Date.now()
|
export function editPost(arn: AnimeNotifier, element: HTMLElement) {
|
||||||
let progress = (time - timeStart) / duration
|
let postId = element.dataset.id
|
||||||
|
|
||||||
if(progress > 1.0) {
|
let render = arn.app.find("render-" + postId)
|
||||||
progress = 1.0
|
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)
|
if(!render.classList.contains("hidden")) {
|
||||||
arn.app.content.parentElement.scrollTop = newScroll
|
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) {
|
// Save post
|
||||||
window.requestAnimationFrame(scroll)
|
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
|
// Forum reply
|
||||||
@ -129,7 +122,7 @@ export function forumReply(arn: AnimeNotifier) {
|
|||||||
tags: []
|
tags: []
|
||||||
}
|
}
|
||||||
|
|
||||||
arn.post("/api/post/new", post)
|
arn.post("/api/new/post", post)
|
||||||
.then(() => arn.reloadContent())
|
.then(() => arn.reloadContent())
|
||||||
.then(() => textarea.value = "")
|
.then(() => textarea.value = "")
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
@ -147,7 +140,7 @@ export function createThread(arn: AnimeNotifier) {
|
|||||||
tags: [category.value]
|
tags: [category.value]
|
||||||
}
|
}
|
||||||
|
|
||||||
arn.post("/api/thread/new", thread)
|
arn.post("/api/new/thread", thread)
|
||||||
.then(() => arn.app.load("/forum/" + thread.tags[0]))
|
.then(() => arn.app.load("/forum/" + thread.tags[0]))
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
}
|
}
|
||||||
@ -168,7 +161,7 @@ export function createSoundTrack(arn: AnimeNotifier, button: HTMLButtonElement)
|
|||||||
button.innerText = "Adding..."
|
button.innerText = "Adding..."
|
||||||
button.disabled = true
|
button.disabled = true
|
||||||
|
|
||||||
arn.post("/api/soundtrack/new", soundtrack)
|
arn.post("/api/new/soundtrack", soundtrack)
|
||||||
.then(() => arn.app.load("/music"))
|
.then(() => arn.app.load("/music"))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
@ -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) {
|
onPopState(e: PopStateEvent) {
|
||||||
if(e.state) {
|
if(e.state) {
|
||||||
this.app.load(e.state, {
|
this.app.load(e.state, {
|
||||||
|
@ -32,5 +32,8 @@ a
|
|||||||
img
|
img
|
||||||
backface-visibility hidden
|
backface-visibility hidden
|
||||||
|
|
||||||
|
.hidden
|
||||||
|
display none !important
|
||||||
|
|
||||||
.spacer
|
.spacer
|
||||||
flex 1
|
flex 1
|
@ -105,6 +105,12 @@
|
|||||||
.post-unlike
|
.post-unlike
|
||||||
color rgb(255, 32, 12) !important
|
color rgb(255, 32, 12) !important
|
||||||
|
|
||||||
|
.post-save
|
||||||
|
//
|
||||||
|
|
||||||
|
.post-input
|
||||||
|
min-height 200px
|
||||||
|
|
||||||
// Old
|
// Old
|
||||||
|
|
||||||
// #posts
|
// #posts
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
mixin input-focus
|
mixin input-focus
|
||||||
:focus
|
:focus
|
||||||
color black
|
|
||||||
border 1px solid input-focus-border-color
|
border 1px solid input-focus-border-color
|
||||||
// TODO: Replace with alpha(main-color, 20%) function
|
// TODO: Replace with alpha(main-color, 20%) function
|
||||||
box-shadow 0 0 6px rgba(248, 165, 130, 0.2)
|
box-shadow 0 0 6px rgba(248, 165, 130, 0.2)
|
||||||
|
2
tests.go
2
tests.go
@ -201,9 +201,11 @@ var interfaceImplementations = map[string][]reflect.Type{
|
|||||||
},
|
},
|
||||||
"Thread": []reflect.Type{
|
"Thread": []reflect.Type{
|
||||||
creatable,
|
creatable,
|
||||||
|
updatable,
|
||||||
},
|
},
|
||||||
"Post": []reflect.Type{
|
"Post": []reflect.Type{
|
||||||
creatable,
|
creatable,
|
||||||
|
updatable,
|
||||||
},
|
},
|
||||||
"SoundTrack": []reflect.Type{
|
"SoundTrack": []reflect.Type{
|
||||||
creatable,
|
creatable,
|
||||||
|
Loading…
Reference in New Issue
Block a user