diff --git a/mixins/Input.pixy b/mixins/Input.pixy index 10511072..2c0b93d1 100644 --- a/mixins/Input.pixy +++ b/mixins/Input.pixy @@ -24,8 +24,9 @@ component InputTags(id string, value []string, label string) .tags(id=id) for index, tag := range value .tag - span.tag-title= tag - .tag-remove.action(data-action="arrayRemove", data-trigger="click", data-field=id, data-index=index) x + span.tag-title.action(contenteditable="true", data-trigger="focusout", data-action="save", data-field=id + "[" + strconv.Itoa(index) + "]")= tag + button.tag-remove.action(data-action="arrayRemove", data-trigger="click", data-field=id, data-index=index) + RawIcon("trash") button.tag-add.action(data-action="arrayAppend", data-trigger="click", data-field=id) RawIcon("plus") diff --git a/mixins/SoundTrack.pixy b/mixins/SoundTrack.pixy index 41931775..b4748642 100644 --- a/mixins/SoundTrack.pixy +++ b/mixins/SoundTrack.pixy @@ -7,13 +7,25 @@ component SoundTrackAllMedia(track *arn.SoundTrack) component SoundTrackMedia(track *arn.SoundTrack, media *arn.ExternalMedia) .sound-track.mountable(id=track.ID) - .sound-track-content + SoundTrackContent(track, media) + SoundTrackFooter(track) + +component SoundTrackContent(track *arn.SoundTrack, media *arn.ExternalMedia) + .sound-track-content + if track.MainAnime() != nil a.sound-track-anime-link.ajax(href="/anime/" + track.MainAnime().ID) img.sound-track-anime-image.lazy(data-src=track.MainAnime().Image.Small, alt=track.MainAnime().Title.Canonical, title=track.MainAnime().Title.Canonical) - - iframe.lazy(data-src=media.EmbedLink(), allowfullscreen="allowfullscreen") - .sound-track-footer - a.ajax(href=track.Link()) - Icon("music") - span posted by - a.ajax(href=track.CreatedByUser().Link())= track.CreatedByUser().Nick + " " \ No newline at end of file + + ExternalMedia(media) + +component SoundTrackFooter(track *arn.SoundTrack) + .sound-track-footer + if track.Title == "" + a.ajax(href=track.Link() + "/edit") untitled + else + a.ajax(href=track.Link())= track.Title + span posted by + a.ajax(href=track.CreatedByUser().Link())= track.CreatedByUser().Nick + " " + +component ExternalMedia(media *arn.ExternalMedia) + iframe.lazy(data-src=media.EmbedLink(), allowfullscreen="allowfullscreen") \ No newline at end of file diff --git a/pages/settings/settings.scarlet b/pages/settings/settings.scarlet index cb0cb0ca..5edda9ce 100644 --- a/pages/settings/settings.scarlet +++ b/pages/settings/settings.scarlet @@ -1,7 +1,6 @@ -.widget-section - button, - .button - margin-bottom 1rem +.widget-section > button, +.widget-section > .button + margin-bottom 1rem .avatar-preview margin 0 auto \ No newline at end of file diff --git a/pages/soundtrack/edit.go b/pages/soundtrack/edit.go index 4b1bcd24..d15a8fac 100644 --- a/pages/soundtrack/edit.go +++ b/pages/soundtrack/edit.go @@ -27,14 +27,17 @@ func Edit(ctx *aero.Context) string { ctx.Data = &arn.OpenGraph{ Tags: map[string]string{ "og:title": track.Title, - "og:image": track.MainAnime().Image.Large, "og:url": "https://" + ctx.App.Config.Domain + track.Link(), "og:site_name": "notify.moe", "og:type": "music.song", }, } - return ctx.HTML(EditForm(track, "Edit soundtrack")) + if track.MainAnime() != nil { + ctx.Data.(*arn.OpenGraph).Tags["og:image"] = track.MainAnime().Image.Large + } + + return ctx.HTML(components.SoundTrackTabs(track) + EditForm(track, "Edit soundtrack")) } // EditForm ... @@ -43,10 +46,11 @@ func EditForm(obj interface{}, title string) string { v := reflect.ValueOf(obj).Elem() id := reflect.Indirect(v.FieldByName("ID")) lowerCaseTypeName := strings.ToLower(t.Name()) + endpoint := `/api/` + lowerCaseTypeName + `/` + id.String() var b bytes.Buffer b.WriteString(`
`) - b.WriteString(`
`) + b.WriteString(`
`) b.WriteString(`

`) b.WriteString(title) b.WriteString(`

`) @@ -86,14 +90,22 @@ func RenderField(b *bytes.Buffer, v *reflect.Value, field reflect.StructField, i b.WriteString(components.InputTags(idPrefix+field.Name, fieldValue.Interface().([]string), field.Name)) case "[]*arn.ExternalMedia": for sliceIndex := 0; sliceIndex < fieldValue.Len(); sliceIndex++ { + b.WriteString(`
`) + b.WriteString(`
` + strconv.Itoa(sliceIndex+1) + ". " + field.Name + `
`) + arrayObj := fieldValue.Index(sliceIndex).Interface() arrayIDPrefix := fmt.Sprintf("%s[%d].", field.Name, sliceIndex) RenderObject(b, arrayObj, arrayIDPrefix) + // Preview + b.WriteString(components.ExternalMedia(fieldValue.Index(sliceIndex).Interface().(*arn.ExternalMedia))) + // Remove button b.WriteString(`
`) + + b.WriteString(`
`) } b.WriteString(`
`) diff --git a/pages/soundtrack/soundtrack.pixy b/pages/soundtrack/soundtrack.pixy index 99bf4654..995a693c 100644 --- a/pages/soundtrack/soundtrack.pixy +++ b/pages/soundtrack/soundtrack.pixy @@ -1,8 +1,29 @@ component Track(track *arn.SoundTrack) - h1= track.Title + SoundTrackTabs(track) + + if track.Title == "" + h1 untitled + else + h1= track.Title .sound-tracks SoundTrackAllMedia(track) - - p - a.ajax(href=track.Link() + "/edit") Edit \ No newline at end of file + + .footer.text-center + if track.EditedBy != "" + span Edited + span.utc-date(data-date=track.Edited) + span by + span= track.EditedByUser().Nick + else + span Posted + span.utc-date(data-date=track.Created) + span by + span= track.CreatedByUser().Nick + + span . + +component SoundTrackTabs(track *arn.SoundTrack) + .tabs + Tab("Soundtrack", "music", track.Link()) + Tab("Edit", "pencil", track.Link() + "/edit") \ No newline at end of file diff --git a/scripts/AnimeNotifier.ts b/scripts/AnimeNotifier.ts index e8641956..d65d6db4 100644 --- a/scripts/AnimeNotifier.ts +++ b/scripts/AnimeNotifier.ts @@ -25,8 +25,8 @@ export class AnimeNotifier { mainPageLoaded: boolean lastReloadContentPath: string - imageFound: MutationQueue - imageNotFound: MutationQueue + elementFound: MutationQueue + elementNotFound: MutationQueue unmount: MutationQueue constructor(app: Application) { @@ -34,13 +34,13 @@ export class AnimeNotifier { this.user = null this.title = "Anime Notifier" - this.imageFound = new MutationQueue(elem => elem.classList.add("image-found")) - this.imageNotFound = new MutationQueue(elem => elem.classList.add("image-not-found")) + this.elementFound = new MutationQueue(elem => elem.classList.add("element-found")) + this.elementNotFound = new MutationQueue(elem => elem.classList.add("element-not-found")) this.unmount = new MutationQueue(elem => elem.classList.remove("mounted")) // These classes will never be removed on DOM diffs Diff.persistentClasses.add("mounted") - Diff.persistentClasses.add("image-found") + Diff.persistentClasses.add("element-found") // Never remove src property on diffs Diff.persistentAttributes.add("src") @@ -134,7 +134,7 @@ export class AnimeNotifier { this.contentLoadedActions = Promise.all([ Promise.resolve().then(() => this.mountMountables()), - Promise.resolve().then(() => this.lazyLoadImages()), + Promise.resolve().then(() => this.lazyLoad()), Promise.resolve().then(() => this.displayLocalDates()), Promise.resolve().then(() => this.setSelectBoxValue()), Promise.resolve().then(() => this.assignActions()), @@ -500,37 +500,59 @@ export class AnimeNotifier { } } - lazyLoadImages() { + lazyLoad() { for(let element of findAll("lazy")) { - this.lazyLoadImage(element as HTMLImageElement) + switch(element.tagName) { + case "IMG": + this.lazyLoadImage(element as HTMLImageElement) + break + + case "IFRAME": + this.lazyLoadIFrame(element as HTMLIFrameElement) + break + } } } - lazyLoadImage(img: HTMLImageElement) { + lazyLoadImage(element: HTMLImageElement) { // Once the image becomes visible, load it - img["became visible"] = () => { + element["became visible"] = () => { // Replace URL with WebP if supported - if(this.webpEnabled && img.dataset.webp) { - let dot = img.dataset.src.lastIndexOf(".") - img.src = img.dataset.src.substring(0, dot) + ".webp" + if(this.webpEnabled && element.dataset.webp) { + let dot = element.dataset.src.lastIndexOf(".") + element.src = element.dataset.src.substring(0, dot) + ".webp" } else { - img.src = img.dataset.src + element.src = element.dataset.src } - if(img.naturalWidth === 0) { - img.onload = () => { - this.imageFound.queue(img) + if(element.naturalWidth === 0) { + element.onload = () => { + this.elementFound.queue(element) } - img.onerror = () => { - this.imageNotFound.queue(img) + element.onerror = () => { + this.elementNotFound.queue(element) } } else { - this.imageFound.queue(img) + this.elementFound.queue(element) } } - this.visibilityObserver.observe(img) + this.visibilityObserver.observe(element) + } + + lazyLoadIFrame(element: HTMLIFrameElement) { + // Once the iframe becomes visible, load it + element["became visible"] = () => { + // If the source is already set correctly, don't set it again to avoid iframe flickering. + if(element.src !== element.dataset.src) { + element.src = element.dataset.src + } + + this.elementFound.queue(element) + } + + this.visibilityObserver.observe(element) } mountMountables() { @@ -752,14 +774,18 @@ export class AnimeNotifier { return } - // Disallow Enter key in contenteditables - if(activeElement.getAttribute("contenteditable") === "true" && e.keyCode == 13) { - if("blur" in activeElement) { - activeElement["blur"]() + // Ignore hotkeys on contentEditable elements + if(activeElement.getAttribute("contenteditable") === "true") { + // Disallow Enter key in contenteditables and make it blur the element instead + if(e.keyCode == 13) { + if("blur" in activeElement) { + activeElement["blur"]() + } + + e.preventDefault() + e.stopPropagation() } - e.preventDefault() - e.stopPropagation() return } diff --git a/styles/images.scarlet b/styles/images.scarlet index bf3ea806..8bc0fc41 100644 --- a/styles/images.scarlet +++ b/styles/images.scarlet @@ -2,10 +2,10 @@ visibility hidden opacity 0 -.image-found +.element-found visibility visible opacity 1 !important -.image-not-found +.element-not-found visibility hidden opacity 0 !important \ No newline at end of file diff --git a/styles/include/config.scarlet b/styles/include/config.scarlet index 7636e3bb..0da623d1 100644 --- a/styles/include/config.scarlet +++ b/styles/include/config.scarlet @@ -62,8 +62,8 @@ content-padding = 1.6rem content-padding-top = 1.6rem content-line-height = 1.7em hover-line-size = 3px -nav-height = 3.11rem typography-margin = 0.4rem +// nav-height = 3.11rem // Timings fade-speed = 250ms diff --git a/styles/tags.scarlet b/styles/tags.scarlet index 45366e80..7c0638a1 100644 --- a/styles/tags.scarlet +++ b/styles/tags.scarlet @@ -1,24 +1,24 @@ +mixin tag-dimensions + padding 0.4rem 0.8rem + margin 0.4rem + height 40px + .tags horizontal-wrap .tag ui-element - padding 0.4rem 0.8rem - margin 0.4rem - -.tag-input - horizontal - - button - margin-left 0.8rem + tag-dimensions + margin-right 0 + border-right none + border-top-right-radius 0 + border-bottom-right-radius 0 .tag-remove - display inline-block - margin-left 0.4rem - opacity 0.5 - - :hover - cursor pointer + tag-dimensions + margin-left 0 + border-top-left-radius 0 + border-bottom-left-radius 0 .tag-add - margin 0.4rem !important \ No newline at end of file + tag-dimensions \ No newline at end of file diff --git a/styles/video.scarlet b/styles/video.scarlet index b6070365..3e84a471 100644 --- a/styles/video.scarlet +++ b/styles/video.scarlet @@ -1,6 +1,9 @@ +iframe + min-height 200px + .video-container width 100% .video width 100% - height calc(100vh - nav-height) \ No newline at end of file + height 100vh \ No newline at end of file diff --git a/sw/service-worker.ts b/sw/service-worker.ts index f48c7eca..456a6645 100644 --- a/sw/service-worker.ts +++ b/sw/service-worker.ts @@ -1,6 +1,6 @@ // pack:ignore -const CACHE = "v-2" +const CACHE = "v-3" const RELOADS = new Map>() const ETAGS = new Map() const CACHEREFRESH = new Map>()