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)
.widget-input
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)
.widget-input
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)
.widget-input
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)
.widget-input
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
.anime-list-container
h3.status-name Watching
AnimeList(animeLists[arn.AnimeListStatusWatching], user)
AnimeList(animeLists[arn.AnimeListStatusWatching], viewUser, user)
if len(animeLists[arn.AnimeListStatusCompleted].Items) > 0
.anime-list-container
h3.status-name Completed
AnimeList(animeLists[arn.AnimeListStatusCompleted], user)
AnimeList(animeLists[arn.AnimeListStatusCompleted], viewUser, user)
if len(animeLists[arn.AnimeListStatusPlanned].Items) > 0
.anime-list-container
h3.status-name Planned
AnimeList(animeLists[arn.AnimeListStatusPlanned], user)
AnimeList(animeLists[arn.AnimeListStatusPlanned], viewUser, user)
if len(animeLists[arn.AnimeListStatusHold].Items) > 0
.anime-list-container
h3.status-name On hold
AnimeList(animeLists[arn.AnimeListStatusHold], user)
AnimeList(animeLists[arn.AnimeListStatusHold], viewUser, user)
if len(animeLists[arn.AnimeListStatusDropped].Items) > 0
.anime-list-container
h3.status-name Dropped
AnimeList(animeLists[arn.AnimeListStatusDropped], user)
AnimeList(animeLists[arn.AnimeListStatusDropped], viewUser, user)
//- for status, animeList := range animeLists
//- h3= status
//- 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
thead
tr
th.anime-list-item-name Anime
th.anime-list-item-airing-date Airing
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
th.anime-list-item-actions Actions
tbody
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
a.ajax(href=item.Link(animeList.User().Nick))= item.Anime().Title.Canonical
td.anime-list-item-airing-date
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)
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-max= item.Anime().EpisodeCountString()
//- .anime-list-item-episodes-edit
//- a.ajax(href=, title="Edit anime")
//- 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
td.anime-list-item-actions
a(href=arn.Nyaa.GetLink(item.Anime()), title="Search on Nyaa", target="_blank", rel="noopener")

View File

@ -46,7 +46,7 @@
// margin-bottom -2px
.anime-list-item-rating
flex-basis 100px
flex-basis 50px
text-align center
.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
.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.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")

View File

@ -26,5 +26,5 @@ func Get(ctx *aero.Context) string {
animeList.Sort()
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) {
arn.loading(true)
let isContentEditable = input.isContentEditable
let obj = {}
let value = input.value
let value = isContentEditable ? input.innerText : input.value
if(input.type === "number") {
if(input.getAttribute("step") === "1") {
obj[input.id] = parseInt(value)
if(input.type === "number" || input.dataset.type === "number") {
if(input.getAttribute("step") === "1" || input.dataset.step === "1") {
obj[input.dataset.field] = parseInt(value)
} else {
obj[input.id] = parseFloat(value)
obj[input.dataset.field] = parseFloat(value)
}
} else {
obj[input.id] = value
obj[input.dataset.field] = value
}
// 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"
}
input.disabled = true
if(isContentEditable) {
input.contentEditable = "false"
} else {
input.disabled = true
}
fetch(apiObject.dataset.api, {
method: "POST",
@ -51,7 +56,12 @@ export function save(arn: AnimeNotifier, input: HTMLInputElement | HTMLTextAreaE
.catch(console.error)
.then(() => {
arn.loading(false)
input.disabled = false
if(isContentEditable) {
input.contentEditable = "true"
} else {
input.disabled = false
}
return arn.reloadContent()
})

View File

@ -265,13 +265,26 @@ export class AnimeNotifier {
}
onKeyDown(e: KeyboardEvent) {
let activeElement = document.activeElement
// Ignore hotkeys on input elements
switch(document.activeElement.tagName) {
switch(activeElement.tagName) {
case "INPUT":
case "TEXTAREA":
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
if(e.keyCode == 70) {
let search = this.app.find("search") as HTMLInputElement
@ -281,6 +294,7 @@ export class AnimeNotifier {
e.preventDefault()
e.stopPropagation()
return
}
}
}

View File

@ -9,3 +9,20 @@ import (
func GetUser(ctx *aero.Context) *arn.User {
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"
}