Inline editing

This commit is contained in:
Eduard Urbach 2017-06-30 23:52:42 +02:00
parent df01fb891b
commit 633b5942f5
8 changed files with 78 additions and 26 deletions

View File

@ -1,19 +1,19 @@
component InputText(id string, value string, label string, placeholder string) component InputText(id string, value string, label string, placeholder string)
.widget-input .widget-input
label(for=id)= label + ":" label(for=id)= label + ":"
input.widget-element.action(id=id, type="text", value=value, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change") input.widget-element.action(id=id, data-field=id, type="text", value=value, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change")
component InputTextArea(id string, value string, label string, placeholder string) component InputTextArea(id string, value string, label string, placeholder string)
.widget-input .widget-input
label(for=id)= label + ":" label(for=id)= label + ":"
textarea.widget-element.action(id=id, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change")= value textarea.widget-element.action(id=id, data-field=id, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change")= value
component InputNumber(id string, value float64, label string, placeholder string, min string, max string, step string) component InputNumber(id string, value float64, label string, placeholder string, min string, max string, step string)
.widget-input .widget-input
label(for=id)= label + ":" label(for=id)= label + ":"
input.widget-element.action(id=id, type="number", value=value, min=min, max=max, step=step, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change") input.widget-element.action(id=id, data-field=id, type="number", value=value, min=min, max=max, step=step, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change")
component InputSelection(id string, value string, label string, placeholder string, values []string) component InputSelection(id string, value string, label string, placeholder string, values []string)
.widget-input .widget-input
label(for=id)= label + ":" label(for=id)= label + ":"
select.widget-element.action(id=id, value=value, title=placeholder, data-action="save", data-trigger="change") select.widget-element.action(id=id, data-field=id, value=value, title=placeholder, data-action="save", data-trigger="change")

View File

@ -4,58 +4,69 @@ component AnimeLists(animeLists map[string]*arn.AnimeList, viewUser *arn.User, u
if len(animeLists[arn.AnimeListStatusWatching].Items) > 0 if len(animeLists[arn.AnimeListStatusWatching].Items) > 0
.anime-list-container .anime-list-container
h3.status-name Watching h3.status-name Watching
AnimeList(animeLists[arn.AnimeListStatusWatching], user) AnimeList(animeLists[arn.AnimeListStatusWatching], viewUser, user)
if len(animeLists[arn.AnimeListStatusCompleted].Items) > 0 if len(animeLists[arn.AnimeListStatusCompleted].Items) > 0
.anime-list-container .anime-list-container
h3.status-name Completed h3.status-name Completed
AnimeList(animeLists[arn.AnimeListStatusCompleted], user) AnimeList(animeLists[arn.AnimeListStatusCompleted], viewUser, user)
if len(animeLists[arn.AnimeListStatusPlanned].Items) > 0 if len(animeLists[arn.AnimeListStatusPlanned].Items) > 0
.anime-list-container .anime-list-container
h3.status-name Planned h3.status-name Planned
AnimeList(animeLists[arn.AnimeListStatusPlanned], user) AnimeList(animeLists[arn.AnimeListStatusPlanned], viewUser, user)
if len(animeLists[arn.AnimeListStatusHold].Items) > 0 if len(animeLists[arn.AnimeListStatusHold].Items) > 0
.anime-list-container .anime-list-container
h3.status-name On hold h3.status-name On hold
AnimeList(animeLists[arn.AnimeListStatusHold], user) AnimeList(animeLists[arn.AnimeListStatusHold], viewUser, user)
if len(animeLists[arn.AnimeListStatusDropped].Items) > 0 if len(animeLists[arn.AnimeListStatusDropped].Items) > 0
.anime-list-container .anime-list-container
h3.status-name Dropped h3.status-name Dropped
AnimeList(animeLists[arn.AnimeListStatusDropped], user) AnimeList(animeLists[arn.AnimeListStatusDropped], viewUser, user)
//- for status, animeList := range animeLists //- for status, animeList := range animeLists
//- h3= status //- h3= status
//- AnimeList(animeList, user) //- AnimeList(animeList, user)
component AnimeList(animeList *arn.AnimeList, user *arn.User) component AnimeList(animeList *arn.AnimeList, viewUser *arn.User, user *arn.User)
table.anime-list table.anime-list
thead thead
tr tr
th.anime-list-item-name Anime th.anime-list-item-name Anime
th.anime-list-item-airing-date Airing th.anime-list-item-airing-date Airing
th.anime-list-item-episodes Episodes th.anime-list-item-episodes Episodes
th.anime-list-item-rating Rating th.anime-list-item-rating Overall
//- th.anime-list-item-rating Story
//- th.anime-list-item-rating Visuals
//- th.anime-list-item-rating Soundtrack
if user != nil if user != nil
th.anime-list-item-actions Actions th.anime-list-item-actions Actions
tbody tbody
each item in animeList.Items each item in animeList.Items
tr.anime-list-item.mountable(title=item.Notes) tr.anime-list-item.mountable(title=item.Notes, data-api="/api/animelist/" + animeList.UserID + "/update/" + item.AnimeID)
td.anime-list-item-name td.anime-list-item-name
a.ajax(href=item.Link(animeList.User().Nick))= item.Anime().Title.Canonical a.ajax(href=item.Link(animeList.User().Nick))= item.Anime().Title.Canonical
td.anime-list-item-airing-date td.anime-list-item-airing-date
if item.Anime().UpcomingEpisode() != nil if item.Anime().UpcomingEpisode() != nil
span.utc-date(data-start-date=item.Anime().UpcomingEpisode().Episode.AiringDate.Start, data-end-date=item.Anime().UpcomingEpisode().Episode.AiringDate.End, data-episode-number=item.Anime().UpcomingEpisode().Episode.Number) span.utc-date(data-start-date=item.Anime().UpcomingEpisode().Episode.AiringDate.Start, data-end-date=item.Anime().UpcomingEpisode().Episode.AiringDate.End, data-episode-number=item.Anime().UpcomingEpisode().Episode.Number)
td.anime-list-item-episodes td.anime-list-item-episodes
.anime-list-item-episodes-watched= item.Episodes .anime-list-item-episodes-watched
.action(contenteditable=utils.SameUser(user, viewUser), data-field="Episodes", data-type="number", data-trigger="focusout", data-action="save")= item.Episodes
.anime-list-item-episodes-separator / .anime-list-item-episodes-separator /
.anime-list-item-episodes-max= item.Anime().EpisodeCountString() .anime-list-item-episodes-max= item.Anime().EpisodeCountString()
//- .anime-list-item-episodes-edit //- .anime-list-item-episodes-edit
//- a.ajax(href=, title="Edit anime") //- a.ajax(href=, title="Edit anime")
//- RawIcon("pencil") //- RawIcon("pencil")
td.anime-list-item-rating= item.FinalRating() td.anime-list-item-rating(title="Overall rating")
.action(contenteditable=utils.SameUser(user, viewUser), data-field="Rating.Overall", data-type="number", data-trigger="focusout", data-action="save")= fmt.Sprintf("%.1f", item.Rating.Overall)
//- td.anime-list-item-rating(title="Story rating")
//- .action(contenteditable=utils.SameUser(user, viewUser), data-field="Rating.Story", data-type="number", data-trigger="focusout", data-action="save")= fmt.Sprintf("%.1f", item.Rating.Story)
//- td.anime-list-item-rating(title="Visuals rating")
//- .action(contenteditable=utils.SameUser(user, viewUser), data-field="Rating.Visuals", data-type="number", data-trigger="focusout", data-action="save")= fmt.Sprintf("%.1f", item.Rating.Visuals)
//- td.anime-list-item-rating(title="Soundtrack rating")
//- .action(contenteditable=utils.SameUser(user, viewUser), data-field="Rating.Soundtrack", data-type="number", data-trigger="focusout", data-action="save")= fmt.Sprintf("%.1f", item.Rating.Soundtrack)
if user != nil if user != nil
td.anime-list-item-actions td.anime-list-item-actions
a(href=arn.Nyaa.GetLink(item.Anime()), title="Search on Nyaa", target="_blank", rel="noopener") a(href=arn.Nyaa.GetLink(item.Anime()), title="Search on Nyaa", target="_blank", rel="noopener")

View File

@ -46,7 +46,7 @@
// margin-bottom -2px // margin-bottom -2px
.anime-list-item-rating .anime-list-item-rating
flex-basis 100px flex-basis 50px
text-align center text-align center
.anime-list-item-actions .anime-list-item-actions

View File

@ -14,7 +14,7 @@ component AnimeListItem(viewUser *arn.User, item *arn.AnimeListItem, anime *arn.
option(value=arn.AnimeListStatusDropped) Dropped option(value=arn.AnimeListStatusDropped) Dropped
.anime-list-item-rating-edit .anime-list-item-rating-edit
InputNumber("Rating.Overall", item.Rating.Overall, "Overall", "Overall rating on a scale of 0 to 10", "0", "10", "0.1") InputNumber("Rating.Overall", item.Rating.Overall, item.OverallRatingName(), "Overall rating on a scale of 0 to 10", "0", "10", "0.1")
InputNumber("Rating.Story", item.Rating.Story, "Story", "Story rating on a scale of 0 to 10", "0", "10", "0.1") InputNumber("Rating.Story", item.Rating.Story, "Story", "Story rating on a scale of 0 to 10", "0", "10", "0.1")
InputNumber("Rating.Visuals", item.Rating.Visuals, "Visuals", "Visuals rating on a scale of 0 to 10", "0", "10", "0.1") InputNumber("Rating.Visuals", item.Rating.Visuals, "Visuals", "Visuals rating on a scale of 0 to 10", "0", "10", "0.1")
InputNumber("Rating.Soundtrack", item.Rating.Soundtrack, "Soundtrack", "Soundtrack rating on a scale of 0 to 10", "0", "10", "0.1") InputNumber("Rating.Soundtrack", item.Rating.Soundtrack, "Soundtrack", "Soundtrack rating on a scale of 0 to 10", "0", "10", "0.1")

View File

@ -26,5 +26,5 @@ func Get(ctx *aero.Context) string {
animeList.Sort() animeList.Sort()
watchingList := animeList.SplitByStatus()[arn.AnimeListStatusWatching] watchingList := animeList.SplitByStatus()[arn.AnimeListStatusWatching]
return utils.AllowEmbed(ctx, ctx.HTML(components.AnimeList(watchingList, user))) return utils.AllowEmbed(ctx, ctx.HTML(components.AnimeList(watchingList, animeList.User(), user)))
} }

View File

@ -6,17 +6,18 @@ import { Diff } from "./Diff"
export function save(arn: AnimeNotifier, input: HTMLInputElement | HTMLTextAreaElement) { export function save(arn: AnimeNotifier, input: HTMLInputElement | HTMLTextAreaElement) {
arn.loading(true) arn.loading(true)
let isContentEditable = input.isContentEditable
let obj = {} let obj = {}
let value = input.value let value = isContentEditable ? input.innerText : input.value
if(input.type === "number") { if(input.type === "number" || input.dataset.type === "number") {
if(input.getAttribute("step") === "1") { if(input.getAttribute("step") === "1" || input.dataset.step === "1") {
obj[input.id] = parseInt(value) obj[input.dataset.field] = parseInt(value)
} else { } else {
obj[input.id] = parseFloat(value) obj[input.dataset.field] = parseFloat(value)
} }
} else { } else {
obj[input.id] = value obj[input.dataset.field] = value
} }
// console.log(input.type, input.dataset.api, obj, JSON.stringify(obj)) // console.log(input.type, input.dataset.api, obj, JSON.stringify(obj))
@ -35,7 +36,11 @@ export function save(arn: AnimeNotifier, input: HTMLInputElement | HTMLTextAreaE
throw "API object not found" throw "API object not found"
} }
if(isContentEditable) {
input.contentEditable = "false"
} else {
input.disabled = true input.disabled = true
}
fetch(apiObject.dataset.api, { fetch(apiObject.dataset.api, {
method: "POST", method: "POST",
@ -51,7 +56,12 @@ export function save(arn: AnimeNotifier, input: HTMLInputElement | HTMLTextAreaE
.catch(console.error) .catch(console.error)
.then(() => { .then(() => {
arn.loading(false) arn.loading(false)
if(isContentEditable) {
input.contentEditable = "true"
} else {
input.disabled = false input.disabled = false
}
return arn.reloadContent() return arn.reloadContent()
}) })

View File

@ -265,13 +265,26 @@ export class AnimeNotifier {
} }
onKeyDown(e: KeyboardEvent) { onKeyDown(e: KeyboardEvent) {
let activeElement = document.activeElement
// Ignore hotkeys on input elements // Ignore hotkeys on input elements
switch(document.activeElement.tagName) { switch(activeElement.tagName) {
case "INPUT": case "INPUT":
case "TEXTAREA": case "TEXTAREA":
return return
} }
// Disallow Enter key in contenteditables
if(activeElement.getAttribute("contenteditable") === "true" && e.keyCode == 13) {
if("blur" in activeElement) {
activeElement["blur"]()
}
e.preventDefault()
e.stopPropagation()
return
}
// F = Search // F = Search
if(e.keyCode == 70) { if(e.keyCode == 70) {
let search = this.app.find("search") as HTMLInputElement let search = this.app.find("search") as HTMLInputElement
@ -281,6 +294,7 @@ export class AnimeNotifier {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
return
} }
} }
} }

View File

@ -9,3 +9,20 @@ import (
func GetUser(ctx *aero.Context) *arn.User { func GetUser(ctx *aero.Context) *arn.User {
return arn.GetUserFromContext(ctx) return arn.GetUserFromContext(ctx)
} }
// SameUser returns "true" or "false" depending on if the users are the same.
func SameUser(a *arn.User, b *arn.User) string {
if a == nil {
return "false"
}
if b == nil {
return "false"
}
if a.ID == b.ID {
return "true"
}
return "false"
}