diff --git a/pom.xml b/pom.xml index 8a38be1476f3f484b49ff693a861d6caf218451e..834323142a111075613d445b2b07255dc7865a92 100644 --- a/pom.xml +++ b/pom.xml @@ -249,6 +249,14 @@ org.springframework.boot spring-boot-starter-undertow + + + + org.springframework.boot + spring-boot-devtools + true + true + diff --git a/src/main/java/com/mtons/mblog/base/storage/impl/AbstractStorage.java b/src/main/java/com/mtons/mblog/base/storage/impl/AbstractStorage.java index 485a437ea14be8c2f64db65f6259f4f5101d2468..77f5c3de8f73e70c629bd0c9f682ca9784a42592 100644 --- a/src/main/java/com/mtons/mblog/base/storage/impl/AbstractStorage.java +++ b/src/main/java/com/mtons/mblog/base/storage/impl/AbstractStorage.java @@ -37,9 +37,9 @@ public abstract class AbstractStorage implements Storage { throw new MtonsException("文件不能为空"); } - if (!FileKit.checkFileType(file.getOriginalFilename())) { - throw new MtonsException("文件格式不支持"); - } +// if (!FileKit.checkMediaFilesType(file.getOriginalFilename())) { +// throw new MtonsException("文件格式不支持"); +// } } @Override diff --git a/src/main/java/com/mtons/mblog/base/utils/BeanMapUtils.java b/src/main/java/com/mtons/mblog/base/utils/BeanMapUtils.java index 6890527e21b5c12e5bb967f64a2daa93a4f5fb4f..254ebdc91e68c1109045aee7dcee41164d0a068e 100644 --- a/src/main/java/com/mtons/mblog/base/utils/BeanMapUtils.java +++ b/src/main/java/com/mtons/mblog/base/utils/BeanMapUtils.java @@ -20,6 +20,12 @@ import org.springframework.beans.BeanUtils; public class BeanMapUtils { private static String[] USER_IGNORE = new String[]{"password", "extend", "roles"}; + public static AnswerFavoriteVO copy(AnswerFavorite po) { + AnswerFavoriteVO ret = new AnswerFavoriteVO(); + BeanUtils.copyProperties(po, ret); + return ret; + } + public static UserVO copy(User po) { if (po == null) { return null; @@ -39,6 +45,18 @@ public class BeanMapUtils { return passport; } + public static AnswerVO copy(Answer an) { + AnswerVO ret = new AnswerVO(); + BeanUtils.copyProperties(an, ret); + return ret; + } + + public static QuestVO copy(Quest qu) { + QuestVO ret = new QuestVO(); + BeanUtils.copyProperties(qu, ret); + return ret; + } + public static CommentVO copy(Comment po) { CommentVO ret = new CommentVO(); BeanUtils.copyProperties(po, ret); diff --git a/src/main/java/com/mtons/mblog/base/utils/FileKit.java b/src/main/java/com/mtons/mblog/base/utils/FileKit.java index bb8e37e228920fcba990b2324d712a9d096e95c3..3ec49d6799311bd0ba24619cdf167e1460aad04a 100644 --- a/src/main/java/com/mtons/mblog/base/utils/FileKit.java +++ b/src/main/java/com/mtons/mblog/base/utils/FileKit.java @@ -5,6 +5,7 @@ import org.apache.commons.io.FileUtils; import javax.validation.constraints.NotNull; import java.io.File; import java.io.IOException; +import java.lang.reflect.Array; import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -15,17 +16,18 @@ import java.util.List; */ public class FileKit { // 文件允许格式 - private static List allowFiles = Arrays.asList(".gif", ".png", ".jpg", ".jpeg", ".bmp"); + private static List allowMediaFiles = Arrays.asList(".gif", ".png", ".jpg", ".jpeg", ".bmp"); + private static List allowFiles = Arrays.asList(".doc", ".docx", "rar", "7z", "zip"); private final static String PREFIX_VIDEO="video/"; private final static String PREFIX_IMAGE="image/"; /** - * 文件类型判断 + * 普通文件类型判断 * * @param fileName * @return */ - public static boolean checkFileType(String fileName) { + public static boolean checkFilesType(String fileName) { Iterator type = allowFiles.iterator(); while (type.hasNext()) { String ext = type.next(); @@ -36,6 +38,23 @@ public class FileKit { return false; } + /** + * 媒体文件类型判断 + * + * @param fileName + * @return + */ + public static boolean checkMediaFilesType(String fileName) { + Iterator type = allowMediaFiles.iterator(); + while (type.hasNext()) { + String ext = type.next(); + if (fileName.toLowerCase().endsWith(ext)) { + return true; + } + } + return false; + } + public static String getFilename(@NotNull String filename) { int pos = filename.lastIndexOf("."); return filename.substring(0, pos); diff --git a/src/main/java/com/mtons/mblog/base/utils/ResourceLock.java b/src/main/java/com/mtons/mblog/base/utils/ResourceLock.java index 6bc46495a39726a2f3c7d9f4877ef579c3028f00..428af7d46aec62e845445a03d3d12a46fcc97326 100644 --- a/src/main/java/com/mtons/mblog/base/utils/ResourceLock.java +++ b/src/main/java/com/mtons/mblog/base/utils/ResourceLock.java @@ -33,6 +33,14 @@ public class ResourceLock { return "POST_OPERATE_{postId}".replace("{postId}", String.valueOf(postId)); } + public static String getQuestKey(Long questId) { + return "QUEST_OPERATE_{questId}".replace("{questId}", String.valueOf(questId)); + } + + public static String getAnswerKey(Long answerId) { + return "ANSWER_OPERATE_{answerId}".replace("{answerId}", String.valueOf(answerId)); + } + public static String getPicKey(Long picId){ return "PIC_OPERATE_{pic}".replace("{pic}", String.valueOf(picId)); } diff --git a/src/main/java/com/mtons/mblog/config/WebMvcConfiguration.java b/src/main/java/com/mtons/mblog/config/WebMvcConfiguration.java index 5098345973f87d0d3fa67d6180b1bbc16870a219..f9718594664dca8ccf261e0fa664bccaa28701da 100644 --- a/src/main/java/com/mtons/mblog/config/WebMvcConfiguration.java +++ b/src/main/java/com/mtons/mblog/config/WebMvcConfiguration.java @@ -4,6 +4,7 @@ import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import com.mtons.mblog.web.interceptor.BaseInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.*; @@ -37,10 +38,12 @@ public class WebMvcConfiguration implements WebMvcConfigurer { public void addResourceHandlers(ResourceHandlerRegistry registry) { String location = "file:///" + siteOptions.getLocation(); registry.addResourceHandler("/dist/**") - .addResourceLocations("classpath:/static/dist/"); + .addResourceLocations("classpath:/static/dist/") + .setCacheControl(CacheControl.noCache()); registry.addResourceHandler("/theme/*/dist/**") .addResourceLocations("classpath:/templates/") - .addResourceLocations(location + "/storage/templates/"); + .addResourceLocations(location + "/storage/templates/") + .setCacheControl(CacheControl.noCache()); registry.addResourceHandler("/storage/avatars/**") .addResourceLocations(location + "/storage/avatars/"); registry.addResourceHandler("/storage/thumbnails/**") diff --git a/src/main/java/com/mtons/mblog/modules/data/AnswerFavoriteVO.java b/src/main/java/com/mtons/mblog/modules/data/AnswerFavoriteVO.java new file mode 100644 index 0000000000000000000000000000000000000000..1da5feb3967f891760946481039cbe1fe1db6d17 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/data/AnswerFavoriteVO.java @@ -0,0 +1,16 @@ +package com.mtons.mblog.modules.data; + +import com.mtons.mblog.modules.entity.AnswerFavorite; + +public class AnswerFavoriteVO extends AnswerFavorite { + + private AnswerVO answer; + + public AnswerVO getAnswer() { + return answer; + } + + public void setAnswer(AnswerVO answer) { + this.answer = answer; + } +} diff --git a/src/main/java/com/mtons/mblog/modules/data/AnswerVO.java b/src/main/java/com/mtons/mblog/modules/data/AnswerVO.java new file mode 100644 index 0000000000000000000000000000000000000000..6849d3a27895afd7ecc4ac4e75749429f3536980 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/data/AnswerVO.java @@ -0,0 +1,61 @@ +package com.mtons.mblog.modules.data; + +import com.alibaba.fastjson.annotation.JSONField; +import com.mtons.mblog.modules.entity.Answer; +import com.mtons.mblog.modules.entity.AnswerAttribute; +import com.mtons.mblog.modules.entity.Quest; + +import java.io.Serializable; + +public class AnswerVO extends Answer implements Serializable { + private static final long serialVersionUID = -6698108607541960362L; + + private String editor; + private String content; + + private UserVO author; + private QuestVO quest; + + @JSONField(serialize = false) + private AnswerAttribute attribute; + + public String getEditor() { + return editor; + } + + public void setEditor(String editor) { + this.editor = editor; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public UserVO getAuthor() { + return author; + } + + public void setAuthor(UserVO author) { + this.author = author; + } + + public QuestVO getQuest() { + return quest; + } + + public void setQuest(QuestVO quest) { + this.quest = quest; + } + + public AnswerAttribute getAttribute() { + return attribute; + } + + public void setAttribute(AnswerAttribute attribute) { + this.attribute = attribute; + } +} diff --git a/src/main/java/com/mtons/mblog/modules/data/QuestVO.java b/src/main/java/com/mtons/mblog/modules/data/QuestVO.java new file mode 100644 index 0000000000000000000000000000000000000000..58baba763f87a82e4eaa7fa2f4641a97b9287ebc --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/data/QuestVO.java @@ -0,0 +1,52 @@ +package com.mtons.mblog.modules.data; + +import com.alibaba.fastjson.annotation.JSONField; +import com.mtons.mblog.modules.entity.PostAttribute; +import com.mtons.mblog.modules.entity.Quest; +import com.mtons.mblog.modules.entity.QuestAttribute; + +import java.io.Serializable; + +public class QuestVO extends Quest implements Serializable { + private static final long serialVersionUID = 7290753603695695506L; + + private String editor; + private String content; + + private UserVO author; + + @JSONField(serialize = false) + private QuestAttribute attribute; + + public String getEditor() { + return editor; + } + + public void setEditor(String editor) { + this.editor = editor; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public UserVO getAuthor() { + return author; + } + + public void setAuthor(UserVO author) { + this.author = author; + } + + public QuestAttribute getAttribute() { + return attribute; + } + + public void setAttribute(QuestAttribute attribute) { + this.attribute = attribute; + } +} diff --git a/src/main/java/com/mtons/mblog/modules/entity/Answer.java b/src/main/java/com/mtons/mblog/modules/entity/Answer.java new file mode 100644 index 0000000000000000000000000000000000000000..18c825ffcbc6dddce273d269136b26ddee081c8a --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/entity/Answer.java @@ -0,0 +1,199 @@ +package com.mtons.mblog.modules.entity; + +import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer; +import org.hibernate.annotations.Filter; +import org.hibernate.annotations.FilterDef; +import org.hibernate.annotations.FilterDefs; +import org.hibernate.annotations.Filters; +import org.hibernate.search.annotations.*; + +import javax.persistence.*; +import javax.persistence.Index; +import java.io.Serializable; +import java.util.Date; + +/** + * 回答表 + * @author Logan + * + */ +@Entity +@Table(name = "mto_answer", indexes = { + @Index(name = "IK_QUEST_ID", columnList = "quest_id") +}) +@FilterDefs({ + @FilterDef(name = "ANSWER_STATUS_FILTER", defaultCondition = "status = 0") +}) +@Filters({ @Filter(name = "ANSWER_STATUS_FILTER") }) +@Indexed(index = "answer") +@Analyzer(impl = SmartChineseAnalyzer.class) +public class Answer implements Serializable { + private static final long serialVersionUID = -5375376562786784962L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @SortableField + @NumericField + private long id; + + + + /** + * 分组/回答ID + */ + @Field + @NumericField + @Column(name = "quest_id", length = 5) + private long questId; + + /** + * 标题 + */ + @Field + @Column(name = "title", length = 64) + private String title; + + /** + * 摘要 + */ + @Field + @Column(length = 140) + private String summary; + + /** + * 作者Id + */ + @Field + @NumericField + @Column(name = "author_id") + private long authorId; + + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + /** + * 收藏数 + */ + private int favors; + + /** + * 评论数 + */ + private int comments; + + /** + * 阅读数 + */ + private int views; + + /** + * 回答状态 + */ + private int status; + + /** + * 推荐状态 + */ + private int featured; + + /** + * 排序值 + */ + private int weight; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getQuestId() { + return questId; + } + + public void setQuestId(long questId) { + this.questId = questId; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public long getAuthorId() { + return authorId; + } + + public void setAuthorId(long authorId) { + this.authorId = authorId; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public int getFavors() { + return favors; + } + + public void setFavors(int favors) { + this.favors = favors; + } + + public int getComments() { + return comments; + } + + public void setComments(int comments) { + this.comments = comments; + } + + public int getViews() { + return views; + } + + public void setViews(int views) { + this.views = views; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public int getFeatured() { + return featured; + } + + public void setFeatured(int featured) { + this.featured = featured; + } + + public int getWeight() { + return weight; + } + + public void setWeight(int weight) { + this.weight = weight; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/src/main/java/com/mtons/mblog/modules/entity/AnswerAttribute.java b/src/main/java/com/mtons/mblog/modules/entity/AnswerAttribute.java new file mode 100644 index 0000000000000000000000000000000000000000..40aaaaba78214d9ecc21232b4567f18d515a1601 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/entity/AnswerAttribute.java @@ -0,0 +1,53 @@ +package com.mtons.mblog.modules.entity; + +import org.hibernate.annotations.Type; + +import javax.persistence.*; +import java.io.Serializable; + +/** + * @author Logan + */ +@Entity +@Table(name = "mto_answer_attribute") +public class AnswerAttribute implements Serializable { + private static final long serialVersionUID = 3185121652186808948L; + + @Id + private long id; + + @Column(length = 16, columnDefinition = "varchar(16) default 'tinymce'") + private String editor; + + /** + * 内容 + */ + @Lob + @Basic(fetch = FetchType.LAZY) + @Type(type="text") + private String content; // 内容 + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getEditor() { + return editor; + } + + public void setEditor(String editor) { + this.editor = editor; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/src/main/java/com/mtons/mblog/modules/entity/AnswerFavorite.java b/src/main/java/com/mtons/mblog/modules/entity/AnswerFavorite.java new file mode 100644 index 0000000000000000000000000000000000000000..a67149c88cac5460bd260fa4326127cee91a5e87 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/entity/AnswerFavorite.java @@ -0,0 +1,61 @@ +package com.mtons.mblog.modules.entity; + +import javax.persistence.*; +import java.util.Date; + +@Entity +@Table(name = "mto_answer_favorite", indexes = { + @Index(name = "IK_USER_ID", columnList = "user_id") +}) +public class AnswerFavorite { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + /** + * 所属用户 + */ + @Column(name = "user_id") + private long userId; + + /** + * 回答ID + */ + @Column(name = "answer_id") + private long answerId; + + @Temporal(TemporalType.TIMESTAMP) + private Date created; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public long getAnswerId() { + return answerId; + } + + public void setAnswerId(long answerId) { + this.answerId = answerId; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } +} diff --git a/src/main/java/com/mtons/mblog/modules/entity/AnswerResource.java b/src/main/java/com/mtons/mblog/modules/entity/AnswerResource.java new file mode 100644 index 0000000000000000000000000000000000000000..48e2b61642a86497d59ac435f5d9066b5c426ccc --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/entity/AnswerResource.java @@ -0,0 +1,33 @@ +package com.mtons.mblog.modules.entity; + +import lombok.Data; + +import javax.persistence.*; +import java.io.Serializable; + +/** + * @author Logan + */ +@Data +@Entity +@Table(name = "mto_answer_resource", indexes = { + @Index(name = "IK_R_ANSWER_ID", columnList = "answer_id") +}) +public class AnswerResource implements Serializable { + private static final long serialVersionUID = 990151006470358653L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(name = "answer_id") + private long answerId; + + private long resourceId; + + private String path; + + @Column(name = "sort", columnDefinition = "int(11) NOT NULL DEFAULT '0'") + private int sort; + +} diff --git a/src/main/java/com/mtons/mblog/modules/entity/Quest.java b/src/main/java/com/mtons/mblog/modules/entity/Quest.java new file mode 100644 index 0000000000000000000000000000000000000000..35dc95678915d8e8608c100a77ce4e0d2d6e2cf9 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/entity/Quest.java @@ -0,0 +1,166 @@ +package com.mtons.mblog.modules.entity; + +import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer; +import org.hibernate.annotations.Filter; +import org.hibernate.annotations.FilterDef; +import org.hibernate.annotations.FilterDefs; +import org.hibernate.annotations.Filters; +import org.hibernate.search.annotations.*; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +/** + * @program: mblog + * @description: 问题实体 + * @author: Mr.Run + * @create: 2021-02-23 01:38 + **/ +@Entity +@Table(name = "mto_quest") +@FilterDefs({ + @FilterDef(name = "QUEST_STATUS_FILTER", defaultCondition = "status = 0" )}) +@Filters({ @Filter(name = "QUEST_STATUS_FILTER") }) +@Indexed(index = "quest") +@Analyzer(impl = SmartChineseAnalyzer.class) +public class Quest implements Serializable { + + private static final long serialVersionUID = -7035746040458241955L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @SortableField + @NumericField + private long id; + + /** + * 标题 + */ + @Field + @Column(name = "title", length = 64) + private String title; + + /** + * 摘要 + */ + @Field + @Column(length = 140) + private String summary; + + /** + * 作者Id + */ + @Field + @NumericField + @Column(name = "author_id") + private long authorId; + + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + /** + * 回答数 + */ + private int answers; + + /** + * 阅读数 + */ + private int views; + + /** + * 问题状态 + */ + private int status; + + /** + * 推荐状态 + */ + private int featured; + + /** + * 排序值 + */ + private int weight; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public long getAuthorId() { + return authorId; + } + + public void setAuthorId(long authorId) { + this.authorId = authorId; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public int getFeatured() { + return featured; + } + + public void setFeatured(int featured) { + this.featured = featured; + } + + public int getWeight() { + return weight; + } + + public void setWeight(int weight) { + this.weight = weight; + } + + public int getAnswers() { + return answers; + } + + public void setAnswers(int answers) { + this.answers = answers; + } + + public int getViews() { + return views; + } + + public void setViews(int views) { + this.views = views; + } +} diff --git a/src/main/java/com/mtons/mblog/modules/entity/QuestAttribute.java b/src/main/java/com/mtons/mblog/modules/entity/QuestAttribute.java new file mode 100644 index 0000000000000000000000000000000000000000..04b2da837e05db6b65bdbf401c4b35de9c941139 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/entity/QuestAttribute.java @@ -0,0 +1,49 @@ +package com.mtons.mblog.modules.entity; + +import org.hibernate.annotations.Type; + +import javax.persistence.*; +import java.io.Serializable; + +@Entity +@Table(name = "mto_quest_attribute") +public class QuestAttribute implements Serializable { + private static final long serialVersionUID = -2226717664731700469L; + @Id + private long id; + + @Column(length = 16, columnDefinition = "varchar(16) default 'tinymce'") + private String editor; + + /** + * 内容 + */ + @Lob + @Basic(fetch = FetchType.LAZY) + @Type(type="text") + private String content; // 内容 + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getEditor() { + return editor; + } + + public void setEditor(String editor) { + this.editor = editor; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/src/main/java/com/mtons/mblog/modules/entity/QuestResource.java b/src/main/java/com/mtons/mblog/modules/entity/QuestResource.java new file mode 100644 index 0000000000000000000000000000000000000000..85cd490d6c0be6e4e6003af286c440f30f58048c --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/entity/QuestResource.java @@ -0,0 +1,30 @@ +package com.mtons.mblog.modules.entity; + +import lombok.Data; + +import javax.persistence.*; +import java.io.Serializable; + +@Data +@Entity +@Table(name = "mto_quest_resource", indexes = { + @Index(name = "IK_R_QUEST_ID", columnList = "quest_id") +}) +public class QuestResource implements Serializable { + + private static final long serialVersionUID = 1992598705931902294L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(name = "quest_id") + private long questId; + + private long resourceId; + + private String path; + + @Column(name = "sort", columnDefinition = "int(11) NOT NULL DEFAULT '0'") + private int sort; +} diff --git a/src/main/java/com/mtons/mblog/modules/repository/AnswerAttributeRepository.java b/src/main/java/com/mtons/mblog/modules/repository/AnswerAttributeRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..d29e1c5c105dea1e49e64fdc9bd42d588e35032c --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/repository/AnswerAttributeRepository.java @@ -0,0 +1,8 @@ +package com.mtons.mblog.modules.repository; + +import com.mtons.mblog.modules.entity.AnswerAttribute; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface AnswerAttributeRepository extends JpaRepository, JpaSpecificationExecutor { +} diff --git a/src/main/java/com/mtons/mblog/modules/repository/AnswerFavoriteRepository.java b/src/main/java/com/mtons/mblog/modules/repository/AnswerFavoriteRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..dc6d3de4e9b96a92eb5aa313532025978696a293 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/repository/AnswerFavoriteRepository.java @@ -0,0 +1,16 @@ +package com.mtons.mblog.modules.repository; + +import com.mtons.mblog.modules.entity.AnswerFavorite; +import com.mtons.mblog.modules.entity.Favorite; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface AnswerFavoriteRepository extends JpaRepository, JpaSpecificationExecutor { + Page findAllByUserId(Pageable pageable, long userId); + + AnswerFavorite findByUserIdAndAnswerId(long userId, long answerId); + + int deleteByAnswerId(long answerId); +} diff --git a/src/main/java/com/mtons/mblog/modules/repository/AnswerRepository.java b/src/main/java/com/mtons/mblog/modules/repository/AnswerRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..18f0efe2f9d41113eafccf6a2b1379c508af59e3 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/repository/AnswerRepository.java @@ -0,0 +1,29 @@ +package com.mtons.mblog.modules.repository; + +import com.mtons.mblog.modules.entity.Answer; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface AnswerRepository extends JpaRepository, JpaSpecificationExecutor { + Page findAllByAuthorId(Pageable pageable, long userId); + + @Query("select coalesce(max(weight), 0) from Answer") + int maxWeight(); + + @Modifying + @Query("update Answer set views = views + :increment where id = :id") + void updateViews(@Param("id") long id, @Param("increment") int increment); + + @Modifying + @Query("update Answer set comments = comments + :increment where id = :id") + void updateComments(@Param("id")long id, @Param("increment") int increment); + + @Modifying + @Query("update Answer set comments = comments + :increment where id = :id") + void updateFavors(@Param("id") long answerId, @Param("increment") int increment); +} diff --git a/src/main/java/com/mtons/mblog/modules/repository/AnswerResourceRepository.java b/src/main/java/com/mtons/mblog/modules/repository/AnswerResourceRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..65f4386a38129a3d49aaf52da83addcaf745efb0 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/repository/AnswerResourceRepository.java @@ -0,0 +1,17 @@ +package com.mtons.mblog.modules.repository; + +import com.mtons.mblog.modules.entity.AnswerAttribute; +import com.mtons.mblog.modules.entity.AnswerResource; +import com.mtons.mblog.modules.entity.PostResource; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +import java.util.List; + +public interface AnswerResourceRepository extends JpaRepository, JpaSpecificationExecutor { + void deleteByAnswerIdAndResourceIdIn(Long answerId, List rids); + + List findByAnswerId(long answerId); + + void deleteByAnswerId(long answerId); +} diff --git a/src/main/java/com/mtons/mblog/modules/repository/QuestAttributeRepository.java b/src/main/java/com/mtons/mblog/modules/repository/QuestAttributeRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..98f1c2d818a5bac2150db6baa91ce36501097644 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/repository/QuestAttributeRepository.java @@ -0,0 +1,8 @@ +package com.mtons.mblog.modules.repository; + +import com.mtons.mblog.modules.entity.QuestAttribute; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface QuestAttributeRepository extends JpaRepository, JpaSpecificationExecutor { +} diff --git a/src/main/java/com/mtons/mblog/modules/repository/QuestRepository.java b/src/main/java/com/mtons/mblog/modules/repository/QuestRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..24406e1eb50f65398f3a2ce5d7a46ed89a9a2aa0 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/repository/QuestRepository.java @@ -0,0 +1,30 @@ +package com.mtons.mblog.modules.repository; + +import com.mtons.mblog.modules.entity.Quest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface QuestRepository extends JpaRepository, JpaSpecificationExecutor { + Page findAllByAuthorId(Pageable pageable, long userId); + + @Query("select coalesce(max(weight), 0) from Quest") + int maxWeight(); + + @Modifying + @Query("update Quest set views = views + :increment where id = :id") + void updateViews(@Param("id") long id, @Param("increment") int identityStep); + + @Modifying + @Query("update Quest set answers = answers + :increment where id = :id") + void updateAnswers(@Param("id") long id, @Param("increment") int identityStep); + + List findAllByStatus(int status, Sort sort); +} diff --git a/src/main/java/com/mtons/mblog/modules/repository/QuestResourceRepository.java b/src/main/java/com/mtons/mblog/modules/repository/QuestResourceRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..4625211e879e07a4dd38cb90a2fc762c2c1bfbcd --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/repository/QuestResourceRepository.java @@ -0,0 +1,21 @@ +package com.mtons.mblog.modules.repository; + +import com.mtons.mblog.modules.entity.PostResource; +import com.mtons.mblog.modules.entity.Quest; +import com.mtons.mblog.modules.entity.QuestResource; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +import java.util.Collection; +import java.util.List; + +public interface QuestResourceRepository extends JpaRepository, JpaSpecificationExecutor { + + int deleteByQuestId(long questId); + + int deleteByQuestIdAndResourceIdIn(long questId, Collection resourceId); + + List findByResourceId(long resourceId); + + List findByQuestId(long questId); +} diff --git a/src/main/java/com/mtons/mblog/modules/service/AnswerFavoriteService.java b/src/main/java/com/mtons/mblog/modules/service/AnswerFavoriteService.java new file mode 100644 index 0000000000000000000000000000000000000000..7891ad6c8a781bfb4197584b5a086928a9fa44dd --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/service/AnswerFavoriteService.java @@ -0,0 +1,20 @@ +package com.mtons.mblog.modules.service; + +import com.mtons.mblog.modules.data.AnswerFavoriteVO; +import com.mtons.mblog.modules.data.FavoriteVO; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface AnswerFavoriteService { + /** + * 查询用户收藏记录 + * @param pageable + * @param userId + * @return + */ + Page pagingByUserId(Pageable pageable, long userId); + + void add(long userId, long answerId); + void delete(long userId, long answerId); + void deleteByAnswerId(long answerId); +} diff --git a/src/main/java/com/mtons/mblog/modules/service/AnswerService.java b/src/main/java/com/mtons/mblog/modules/service/AnswerService.java new file mode 100644 index 0000000000000000000000000000000000000000..00ca465631cd1a6690f7b8a13122aa56573afe7b --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/service/AnswerService.java @@ -0,0 +1,151 @@ +package com.mtons.mblog.modules.service; + +import com.mtons.mblog.base.lang.Consts; +import com.mtons.mblog.modules.data.AnswerVO; +import com.mtons.mblog.modules.entity.Answer; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Logan + */ +@CacheConfig(cacheNames = Consts.CACHE_USER) +public interface AnswerService { + /** + * 分页查询所有回答 + * + * @param pageable + * @param questId 问题Id + */ + @Cacheable + Page paging(Pageable pageable, long questId, Set excludeQuestIds); + + Page paging4Admin(Pageable pageable, long questId, String title); + + /** + * 查询个人发布回答 + * @param pageable + * @param userId + */ + @Cacheable + Page pagingByAuthorId(Pageable pageable, long userId); + + /** + * 查询最近更新 - 按发布时间排序 + * @param maxResults + * @return + */ + @Cacheable(key = "'latest_' + #maxResults") + List findLatestAnswer(int maxResults); + + /** + * 查询热门回答 - 按浏览次数排序 + * @param maxResults + * @return + */ + @Cacheable(key = "'hottest_' + #maxResults") + List findHottestAnswer(int maxResults); + + List findHottestAnswerByQustId(long questId, int maxResults, long excludeAnswerId); + + /** + * 根据Ids查询 + * @param ids + * @return + */ + Map findMapByIds(Set ids); + + /** + * 发布回答 + * @param answer + */ + @CacheEvict(allEntries = true) + long post(AnswerVO answer); + + /** + * 回答详情 + * @param id + * @return + */ + @Cacheable(key = "'answer_' + #id") + AnswerVO get(long id); + + /** + * 更新回答方法 + * @param a + */ + @CacheEvict(allEntries = true) + void update(AnswerVO a); + + /** + * 推荐/精华 + * @param id + * @param featured 0: 取消, 1: 加精 + */ + @CacheEvict(allEntries = true) + void updateFeatured(long id, int featured); + + /** + * 置顶 + * @param id + * @param weighted 0: 取消, 1: 置顶 + */ + @CacheEvict(allEntries = true) + void updateWeight(long id, int weighted); + + /** + * 带作者验证的删除 - 验证是否属于自己的回答 + * @param id + * @param authorId + */ + @CacheEvict(allEntries = true) + void delete(long id, long authorId); + + /** + * 批量删除回答, 且刷新缓存 + * + * @param ids + */ + @CacheEvict(allEntries = true) + void delete(Collection ids); + + /** + * 自增浏览数 + * @param id + */ + @CacheEvict(key = "'view_' + #id") + void identityViews(long id); + + /** + * 自增评论数 + * @param id + */ + @CacheEvict(key = "'view_' + #id") + void identityComments(long id); + + /** + * 喜欢回答 + * @param userId + * @param answerId + */ + @CacheEvict(key = "'view_' + #answerId") + void favor(long userId, long answerId); + + /** + * 取消喜欢回答 + * @param userId + * @param answerId + */ + @CacheEvict(key = "'view_' + #answerId") + void unfavor(long userId, long answerId); + + long count(); +} diff --git a/src/main/java/com/mtons/mblog/modules/service/QuestService.java b/src/main/java/com/mtons/mblog/modules/service/QuestService.java new file mode 100644 index 0000000000000000000000000000000000000000..929d03c07fa7bddb2053a764858eb781b631aae5 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/service/QuestService.java @@ -0,0 +1,134 @@ +package com.mtons.mblog.modules.service; + +import com.mtons.mblog.base.lang.Consts; +import com.mtons.mblog.modules.data.QuestVO; +import com.mtons.mblog.modules.entity.Channel; +import com.mtons.mblog.modules.entity.Quest; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@CacheConfig(cacheNames = Consts.CACHE_USER) +public interface QuestService { + /** + * 分页查询所有问题 + * + * @param pageable + */ + @Cacheable + Page paging(Pageable pageable); + + Page paging4Admin(Pageable pageable, String title); + + /** + * 查询个人发布问题 + * @param pageable + * @param userId + */ + @Cacheable + Page pagingByAuthorId(Pageable pageable, long userId); + + /** + * 查询最近更新 - 按发布时间排序 + * @param maxResults + * @return + */ + @Cacheable(key = "'latest_' + #maxResults") + List findLatestQuests(int maxResults); + + /** + * 查询热门文章 - 按浏览次数排序 + * @param maxResults + * @return + */ + @Cacheable(key = "'hottest_' + #maxResults") + List findHottestQuests(int maxResults); + + /** + * 根据Ids查询 + * @param ids + * @return + */ + Map findMapByIds(Set ids); + + /** + * 发布问题 + * @param quest + */ + @CacheEvict(allEntries = true) + long post(QuestVO quest); + + /** + * 问题详情 + * @param id + * @return + */ + @Cacheable(key = "'post_' + #id") + QuestVO get(long id); + + /** + * 更新问题方法 + * @param q + */ + @CacheEvict(allEntries = true) + void update(QuestVO q); + + /** + * 推荐/精华 + * @param id + * @param featured 0: 取消, 1: 加精 + */ + @CacheEvict(allEntries = true) + void updateFeatured(long id, int featured); + + /** + * 置顶 + * @param id + * @param weighted 0: 取消, 1: 置顶 + */ + @CacheEvict(allEntries = true) + void updateWeight(long id, int weighted); + + /** + * 带作者验证的删除 - 验证是否属于自己的问题 + * @param id + * @param authorId + */ + @CacheEvict(allEntries = true) + void delete(long id, long authorId); + + /** + * 批量删除问题, 且刷新缓存 + * + * @param ids + */ + @CacheEvict(allEntries = true) + void delete(Collection ids); + + /** + * 自增浏览数 + * @param id + */ + @CacheEvict(key = "'view_' + #id") + void identityViews(long id); + + /** + * 自增回答数 + * @param id + */ + @CacheEvict(key = "'view_' + #id") + void identityAnswers(long id); + + long count(); + + List findAll(int status); + + Quest getById(long questId); +} diff --git a/src/main/java/com/mtons/mblog/modules/service/impl/AnswerFavoriteServiceImpl.java b/src/main/java/com/mtons/mblog/modules/service/impl/AnswerFavoriteServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..cefbb18e2e4222077bd11fa390276df1d6cc162f --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/service/impl/AnswerFavoriteServiceImpl.java @@ -0,0 +1,86 @@ +package com.mtons.mblog.modules.service.impl; + +import com.mtons.mblog.base.utils.BeanMapUtils; +import com.mtons.mblog.modules.data.AnswerFavoriteVO; +import com.mtons.mblog.modules.data.AnswerVO; +import com.mtons.mblog.modules.data.FavoriteVO; +import com.mtons.mblog.modules.data.PostVO; +import com.mtons.mblog.modules.entity.AnswerFavorite; +import com.mtons.mblog.modules.entity.Favorite; +import com.mtons.mblog.modules.repository.AnswerFavoriteRepository; +import com.mtons.mblog.modules.service.AnswerFavoriteService; +import com.mtons.mblog.modules.service.AnswerService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import java.util.*; + +@Slf4j +@Service +@Transactional(readOnly = true) +public class AnswerFavoriteServiceImpl implements AnswerFavoriteService { + + @Autowired + private AnswerFavoriteRepository answerFavoriteRepository; + @Autowired + private AnswerService answerService; + + @Override + public Page pagingByUserId(Pageable pageable, long userId) { + Page page = answerFavoriteRepository.findAllByUserId(pageable, userId); + + List rets = new ArrayList<>(); + Set answerIds = new HashSet<>(); + for (AnswerFavorite po : page.getContent()) { + rets.add(BeanMapUtils.copy(po)); + answerIds.add(po.getAnswerId()); + } + + if (answerIds.size() > 0) { + Map answers = answerService.findMapByIds(answerIds); + + for (AnswerFavoriteVO t : rets) { + AnswerVO p = answers.get(t.getAnswerId()); + t.setAnswer(p); + } + } + return new PageImpl<>(rets, pageable, page.getTotalElements()); + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void add(long userId, long answerId) { + AnswerFavorite po = answerFavoriteRepository.findByUserIdAndAnswerId(userId, answerId); + + Assert.isNull(po, "您已经收藏过此回答"); + + // 如果没有喜欢过, 则添加记录 + po = new AnswerFavorite(); + po.setUserId(userId); + po.setAnswerId(answerId); + po.setCreated(new Date()); + + answerFavoriteRepository.save(po); + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void delete(long userId, long answerId) { + AnswerFavorite po = answerFavoriteRepository.findByUserIdAndAnswerId(userId, answerId); + Assert.notNull(po, "还没有喜欢过此回答"); + answerFavoriteRepository.delete(po); + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void deleteByAnswerId(long answerId) { + int rows = answerFavoriteRepository.deleteByAnswerId(answerId); + log.info("answerFavoriteRepository delete {}", rows); + } +} diff --git a/src/main/java/com/mtons/mblog/modules/service/impl/AnswerServiceImpl.java b/src/main/java/com/mtons/mblog/modules/service/impl/AnswerServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..58c47ae80cc7a55b14b8ff29d587e76788c28d93 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/service/impl/AnswerServiceImpl.java @@ -0,0 +1,513 @@ +package com.mtons.mblog.modules.service.impl; + +import com.mtons.mblog.base.lang.Consts; +import com.mtons.mblog.base.utils.BeanMapUtils; +import com.mtons.mblog.base.utils.MarkdownUtils; +import com.mtons.mblog.base.utils.PreviewTextUtils; +import com.mtons.mblog.base.utils.ResourceLock; +import com.mtons.mblog.modules.aspect.PostStatusFilter; +import com.mtons.mblog.modules.data.AnswerVO; +import com.mtons.mblog.modules.data.PostVO; +import com.mtons.mblog.modules.data.QuestVO; +import com.mtons.mblog.modules.data.UserVO; +import com.mtons.mblog.modules.entity.*; +import com.mtons.mblog.modules.event.PostUpdateEvent; +import com.mtons.mblog.modules.repository.*; +import com.mtons.mblog.modules.service.AnswerFavoriteService; +import com.mtons.mblog.modules.service.AnswerService; +import com.mtons.mblog.modules.service.QuestService; +import com.mtons.mblog.modules.service.UserService; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.ListUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.data.domain.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import javax.persistence.criteria.Predicate; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Service +@Transactional +public class AnswerServiceImpl implements AnswerService { + + @Autowired + private AnswerRepository answerRepository; + @Autowired + private UserService userService; + @Autowired + private QuestService questService; + @Autowired + private QuestRepository questRepository; + @Autowired + private AnswerAttributeRepository answerAttributeRepository; + @Autowired + private ResourceRepository resourceRepository; + @Autowired + private AnswerResourceRepository answerResourceRepository; + @Autowired + private ApplicationContext applicationContext; + @Autowired + private AnswerFavoriteService answerFavoriteService; + + private static Pattern pattern = Pattern.compile("(?<=/_signature/)(.+?)(?=\\.)"); + + @Override + @PostStatusFilter + public Page paging(Pageable pageable, long questId, Set excludeQuestIds) { + Page page = answerRepository.findAll((root, query, builder) -> { + Predicate predicate = builder.conjunction(); + + if (questId > Consts.ZERO) { + predicate.getExpressions().add( + builder.equal(root.get("questId").as(Long.class), questId)); + } + + if (null != excludeQuestIds && !excludeQuestIds.isEmpty()) { + predicate.getExpressions().add( + builder.not(root.get("questId").in(excludeQuestIds))); + } + +// predicate.getExpressions().add( +// builder.equal(root.get("featured").as(Integer.class), Consts.FEATURED_DEFAULT)); + + return predicate; + }, pageable); + + return new PageImpl<>(toAnswer(page.getContent()), pageable, page.getTotalElements()); + } + + private List toAnswer(List answers) { + HashSet uids = new HashSet<>(); + HashSet groupIds = new HashSet<>(); + + List rets = answers + .stream() + .map(an -> { + uids.add(an.getAuthorId()); + groupIds.add(an.getQuestId()); + return BeanMapUtils.copy(an); + }) + .collect(Collectors.toList()); + + // 加载用户信息 + buildUsers(rets, uids); + buildGroups(rets, groupIds); + + return rets; + } + + private void buildUsers(Collection answers, Set uids) { + Map userMap = userService.findMapByIds(uids); + answers.forEach(p -> p.setAuthor(userMap.get(p.getAuthorId()))); + } + + private void buildGroups(Collection answers, Set groupIds) { + Map map = questService.findMapByIds(groupIds); + answers.forEach(p -> p.setQuest(map.get(p.getQuestId()))); + } + + @Override + public Page paging4Admin(Pageable pageable, long questId, String title) { + Page page = answerRepository.findAll((root, query, builder) -> { + Predicate predicate = builder.conjunction(); + if (questId > Consts.ZERO) { + predicate.getExpressions().add( + builder.equal(root.get("questId").as(Long.class), questId)); + } + if (StringUtils.isNotBlank(title)) { + predicate.getExpressions().add( + builder.like(root.get("title").as(String.class), "%" + title + "%")); + } + return predicate; + }, pageable); + + return new PageImpl<>(toAnswer(page.getContent()), pageable, page.getTotalElements()); + } + + @Override + @PostStatusFilter + public Page pagingByAuthorId(Pageable pageable, long userId) { + Page page = answerRepository.findAllByAuthorId(pageable, userId); + return new PageImpl<>(toAnswer(page.getContent()), pageable, page.getTotalElements()); + } + + @Override + @PostStatusFilter + public List findLatestAnswer(int maxResults) { + return find("created", maxResults).stream().map(BeanMapUtils::copy).collect(Collectors.toList()); + } + + @PostStatusFilter + private List find(String orderBy, int size) { + Pageable pageable = PageRequest.of(0, size, Sort.by(Sort.Direction.DESC, orderBy)); + + Set excludeQuestIds = new HashSet<>(); + + List quests = questService.findAll(Consts.STATUS_CLOSED); + if (quests != null) { + quests.forEach((c) -> excludeQuestIds.add(c.getId())); + } + + Page page = answerRepository.findAll((root, query, builder) -> { + Predicate predicate = builder.conjunction(); + if (excludeQuestIds.size() > 0) { + predicate.getExpressions().add( + builder.not(root.get("questId").in(excludeQuestIds))); + } + return predicate; + }, pageable); + return page.getContent(); + } + + @PostStatusFilter + private List find(String orderBy, int size, long questId, long excludeAnswerId) { + Pageable pageable = PageRequest.of(0, size, Sort.by(Sort.Direction.DESC, orderBy)); + + Set excludeAnswerIds = new HashSet<>(); + + Answer answer = answerRepository.findById(excludeAnswerId).get(); + if (answer != null) { + excludeAnswerIds.add(answer.getId()); + } + + Page page = answerRepository.findAll((root, query, builder) -> { + Predicate predicate = builder.conjunction(); + if (excludeAnswerId > 0) { + predicate.getExpressions().add( + builder.not(root.get("id").in(excludeAnswerIds))); + } + if (questId > 0) { + predicate.getExpressions().add(builder.equal(root.get("questId"), questId)); + } + return predicate; + }, pageable); + return page.getContent(); + } + + @Override + @PostStatusFilter + public List findHottestAnswer(int maxResults) { + return find("views", maxResults).stream().map(BeanMapUtils::copy).collect(Collectors.toList()); + } + + @Override + @PostStatusFilter + public List findHottestAnswerByQustId(long questId, int maxResults, long excludeAnswerId) { + return find("views", maxResults, questId, excludeAnswerId).stream().map(BeanMapUtils::copy).collect(Collectors.toList()); + } + + @Override + @PostStatusFilter + public Map findMapByIds(Set ids) { + if (ids == null || ids.isEmpty()) { + return Collections.emptyMap(); + } + + List list = answerRepository.findAllById(ids); + Map rets = new HashMap<>(); + + HashSet uids = new HashSet<>(); + + list.forEach(an -> { + rets.put(an.getId(), BeanMapUtils.copy(an)); + uids.add(an.getAuthorId()); + }); + + // 加载用户信息 + buildUsers(rets.values(), uids); + return rets; + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public long post(AnswerVO answer) { + Answer an = new Answer(); + + BeanUtils.copyProperties(answer, an); + + an.setCreated(new Date()); + an.setStatus(answer.getStatus()); + + // 处理摘要 + if (StringUtils.isBlank(answer.getSummary())) { + an.setSummary(trimSummary(answer.getEditor(), answer.getContent())); + } else { + an.setSummary(answer.getSummary()); + } + + answerRepository.save(an); + + String key = ResourceLock.getAnswerKey(an.getId()); + AtomicInteger lock = ResourceLock.getAtomicInteger(key); + try { + synchronized (lock){ + AnswerAttribute attr = new AnswerAttribute(); + attr.setContent(answer.getContent()); + attr.setEditor(answer.getEditor()); + attr.setId(an.getId()); + answerAttributeRepository.save(attr); + + countResource(an.getId(), null, attr.getContent()); + onPushEvent(an, PostUpdateEvent.ACTION_PUBLISH); + return an.getId(); + } + }finally { + ResourceLock.giveUpAtomicInteger(key); + } + + } + + private void onPushEvent(Answer answer, int action) { + PostUpdateEvent event = new PostUpdateEvent(System.currentTimeMillis()); + event.setPostId(answer.getId()); + event.setUserId(answer.getAuthorId()); + event.setAction(action); + applicationContext.publishEvent(event); + } + + private void countResource(Long answerId, String originContent, String newContent){ + if (StringUtils.isEmpty(originContent)){ + originContent = ""; + } + if (StringUtils.isEmpty(newContent)){ + newContent = ""; + } + + Set exists = extractImageMd5(originContent); + Set news = extractImageMd5(newContent); + + List adds = ListUtils.removeAll(news, exists); + List deleteds = ListUtils.removeAll(exists, news); + + if (adds.size() > 0) { + List resources = resourceRepository.findByMd5In(adds); + + List ars = resources.stream().map(n -> { + AnswerResource ar = new AnswerResource(); + ar.setResourceId(n.getId()); + ar.setAnswerId(answerId); + ar.setPath(n.getPath()); + return ar; + }).collect(Collectors.toList()); + answerResourceRepository.saveAll(ars); + + resourceRepository.updateAmount(adds, 1); + } + + if (deleteds.size() > 0) { + List resources = resourceRepository.findByMd5In(deleteds); + List rids = resources.stream().map(Resource::getId).collect(Collectors.toList()); + answerResourceRepository.deleteByAnswerIdAndResourceIdIn(answerId, rids); + resourceRepository.updateAmount(deleteds, -1); + } + } + + private Set extractImageMd5(String text) { +// Pattern pattern = Pattern.compile("(?<=/_signature/)[^/]+?jpg"); + + Set md5s = new HashSet<>(); + + Matcher originMatcher = pattern.matcher(text); + while (originMatcher.find()) { + String key = originMatcher.group(); +// md5s.add(key.substring(0, key.lastIndexOf("."))); + md5s.add(key); + } + + return md5s; + } + + /** + * 截取回答内容 + * @param text + * @return + */ + private String trimSummary(String editor, final String text){ + if (Consts.EDITOR_MARKDOWN.endsWith(editor)) { + return PreviewTextUtils.getText(MarkdownUtils.renderMarkdown(text), 126); + } else { + return PreviewTextUtils.getText(text, 126); + } + } + + @Override + public AnswerVO get(long id) { + Optional an = answerRepository.findById(id); + if (an.isPresent()) { + AnswerVO d = BeanMapUtils.copy(an.get()); + + d.setAuthor(userService.get(d.getAuthorId())); + d.setQuest(questService.get(d.getQuestId())); + + AnswerAttribute attr = answerAttributeRepository.findById(d.getId()).get(); + d.setContent(attr.getContent()); + d.setEditor(attr.getEditor()); + return d; + } + return null; + } + + /** + * 更新回答方法 + * @param a + */ + @Override + @Transactional(rollbackFor = Throwable.class) + public void update(AnswerVO a) { + Optional optional = answerRepository.findById(a.getId()); + + if (optional.isPresent()) { + String key = ResourceLock.getAnswerKey(a.getId()); + AtomicInteger lock = ResourceLock.getAtomicInteger(key); + try { + synchronized (lock){ + Answer an = optional.get(); + an.setTitle(a.getTitle());//标题 + an.setQuestId(a.getQuestId()); + an.setStatus(a.getStatus()); + + // 处理摘要 + if (StringUtils.isBlank(a.getSummary())) { + an.setSummary(trimSummary(a.getEditor(), a.getContent())); + } else { + an.setSummary(a.getSummary()); + } + + + // 保存扩展 + Optional attributeOptional = answerAttributeRepository.findById(an.getId()); + String originContent = ""; + if (attributeOptional.isPresent()){ + originContent = attributeOptional.get().getContent(); + } + AnswerAttribute attr = new AnswerAttribute(); + attr.setContent(a.getContent()); + attr.setEditor(a.getEditor()); + attr.setId(an.getId()); + answerAttributeRepository.save(attr); + + countResource(an.getId(), originContent, a.getContent()); + } + }finally { + ResourceLock.giveUpAtomicInteger(key); + } + } + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void updateFeatured(long id, int featured) { + Answer an = answerRepository.findById(id).get(); + int status = Consts.FEATURED_ACTIVE == featured ? Consts.FEATURED_ACTIVE: Consts.FEATURED_DEFAULT; + an.setFeatured(status); + answerRepository.save(an); + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void updateWeight(long id, int weighted) { + Answer an = answerRepository.findById(id).get(); + + int max = Consts.ZERO; + if (Consts.FEATURED_ACTIVE == weighted) { + max = answerRepository.maxWeight() + 1; + } + an.setWeight(max); + answerRepository.save(an); + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void delete(long id, long authorId) { + Answer an = answerRepository.findById(id).get(); + // 判断文章是否属于当前登录用户 + Assert.isTrue(an.getAuthorId() == authorId, "认证失败"); + + String key = ResourceLock.getAnswerKey(an.getId()); + AtomicInteger lock = ResourceLock.getAtomicInteger(key); + try { + synchronized (lock){ + answerRepository.deleteById(id); + answerAttributeRepository.deleteById(id); + cleanResource(an.getId()); + onPushEvent(an, PostUpdateEvent.ACTION_DELETE); + } + }finally { + ResourceLock.giveUpAtomicInteger(key); + } + } + + private void cleanResource(long answerId) { + List list = answerResourceRepository.findByAnswerId(answerId); + if (null == list || list.isEmpty()) { + return; + } + List rids = list.stream().map(AnswerResource::getResourceId).collect(Collectors.toList()); + resourceRepository.updateAmountByIds(rids, -1); + answerResourceRepository.deleteByAnswerId(answerId); + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void delete(Collection ids) { + if (CollectionUtils.isNotEmpty(ids)) { + List list = answerRepository.findAllById(ids); + list.forEach(an -> { + String key = ResourceLock.getAnswerKey(an.getId()); + AtomicInteger lock = ResourceLock.getAtomicInteger(key); + try { + synchronized (lock){ + answerRepository.delete(an); + answerAttributeRepository.deleteById(an.getId()); + cleanResource(an.getId()); + onPushEvent(an, PostUpdateEvent.ACTION_DELETE); + } + }finally { + ResourceLock.giveUpAtomicInteger(key); + } + }); + } + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void identityViews(long id) { + // 不清理缓存, 等待文章缓存自动过期 + answerRepository.updateViews(id, Consts.IDENTITY_STEP); + } + + @Override + @Transactional + public void identityComments(long id) { + answerRepository.updateComments(id, Consts.IDENTITY_STEP); + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void favor(long userId, long answerId) { + answerRepository.updateFavors(answerId, Consts.IDENTITY_STEP); + answerFavoriteService.add(userId, answerId); + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void unfavor(long userId, long answerId) { + answerRepository.updateFavors(answerId, Consts.DECREASE_STEP); + answerFavoriteService.delete(userId, answerId); + } + + @Override + @PostStatusFilter + public long count() { + return answerRepository.count(); + } +} diff --git a/src/main/java/com/mtons/mblog/modules/service/impl/QuestServiceImpl.java b/src/main/java/com/mtons/mblog/modules/service/impl/QuestServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..1ece50edcd3413993d13d8cf038d02d16d7e5aea --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/service/impl/QuestServiceImpl.java @@ -0,0 +1,436 @@ +package com.mtons.mblog.modules.service.impl; + +import com.mtons.mblog.base.lang.Consts; +import com.mtons.mblog.base.utils.BeanMapUtils; +import com.mtons.mblog.base.utils.MarkdownUtils; +import com.mtons.mblog.base.utils.PreviewTextUtils; +import com.mtons.mblog.base.utils.ResourceLock; +import com.mtons.mblog.modules.aspect.PostStatusFilter; +import com.mtons.mblog.modules.data.PostVO; +import com.mtons.mblog.modules.data.QuestVO; +import com.mtons.mblog.modules.data.UserVO; +import com.mtons.mblog.modules.entity.*; +import com.mtons.mblog.modules.event.PostUpdateEvent; +import com.mtons.mblog.modules.repository.QuestAttributeRepository; +import com.mtons.mblog.modules.repository.QuestRepository; +import com.mtons.mblog.modules.repository.QuestResourceRepository; +import com.mtons.mblog.modules.repository.ResourceRepository; +import com.mtons.mblog.modules.service.QuestService; +import com.mtons.mblog.modules.service.UserService; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.ListUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.data.domain.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import javax.persistence.criteria.Predicate; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Service +@Transactional +public class QuestServiceImpl implements QuestService { + + @Autowired + private QuestRepository questRepository; + @Autowired + private UserService userService; + @Autowired + private QuestAttributeRepository questAttributeRepository; + @Autowired + private ResourceRepository resourceRepository; + @Autowired + private QuestResourceRepository questResourceRepository; + @Autowired + private ApplicationContext applicationContext; + + private static Pattern pattern = Pattern.compile("(?<=/_signature/)(.+?)(?=\\.)"); + + @Override + @PostStatusFilter + public Page paging(Pageable pageable) { + Page page = questRepository.findAll((root, query, builder) -> { + Predicate predicate = builder.conjunction(); + + return predicate; + }, pageable); + return new PageImpl<>(toQuests(page.getContent()), pageable, page.getTotalElements()); + } + + private List toQuests(List quests) { + HashSet uids = new HashSet<>(); + + List rets = quests + .stream() + .map(qu -> { + uids.add(qu.getAuthorId()); + return BeanMapUtils.copy(qu); + }) + .collect(Collectors.toList()); + + // 加载用户信息 + buildUsers(rets, uids); + + return rets; + } + + private void buildUsers(Collection quests, Set uids) { + Map userMap = userService.findMapByIds(uids); + quests.forEach(p -> p.setAuthor(userMap.get(p.getAuthorId()))); + } + + @Override + public Page paging4Admin(Pageable pageable, String title) { + Page page = questRepository.findAll((root, query, builder) -> { + Predicate predicate = builder.conjunction(); + + if (StringUtils.isNotBlank(title)) { + predicate.getExpressions().add( + builder.like(root.get("title").as(String.class), "%" + title + "%")); + } + return predicate; + }, pageable); + + return new PageImpl<>(toQuests(page.getContent()), pageable, page.getTotalElements()); + } + + @Override + public Page pagingByAuthorId(Pageable pageable, long userId) { + Page page = questRepository.findAllByAuthorId(pageable, userId); + return new PageImpl<>(toQuests(page.getContent()), pageable, page.getTotalElements()); + } + + @Override + public List findLatestQuests(int maxResults) { + return find("created", maxResults).stream().map(BeanMapUtils::copy).collect(Collectors.toList()); + } + + @PostStatusFilter + private List find(String orderBy, int size) { + Pageable pageable = PageRequest.of(0, size, Sort.by(Sort.Direction.DESC, orderBy)); + + Page page = questRepository.findAll((root, query, builder) -> { + Predicate predicate = builder.conjunction(); + return predicate; + }, pageable); + return page.getContent(); + } + + @Override + public List findHottestQuests(int maxResults) { + return find("views", maxResults).stream().map(BeanMapUtils::copy).collect(Collectors.toList()); + } + + @Override + public Map findMapByIds(Set ids) { + if (ids == null || ids.isEmpty()) { + return Collections.emptyMap(); + } + + List list = questRepository.findAllById(ids); + Map rets = new HashMap<>(); + + HashSet uids = new HashSet<>(); + + list.forEach(qu -> { + rets.put(qu.getId(), BeanMapUtils.copy(qu)); + uids.add(qu.getAuthorId()); + }); + + // 加载用户信息 + buildUsers(rets.values(), uids); + return rets; + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public long post(QuestVO quest) { + Quest qu = new Quest(); + + BeanUtils.copyProperties(quest, qu); + + qu.setCreated(new Date()); + qu.setStatus(quest.getStatus()); + + // 处理摘要 + if (StringUtils.isBlank(quest.getSummary())) { + qu.setSummary(trimSummary(quest.getEditor(), quest.getContent())); + } else { + qu.setSummary(quest.getSummary()); + } + + questRepository.save(qu); + + String key = ResourceLock.getQuestKey(qu.getId()); + AtomicInteger lock = ResourceLock.getAtomicInteger(key); + try { + synchronized (lock){ + QuestAttribute attr = new QuestAttribute(); + attr.setContent(quest.getContent()); + attr.setEditor(quest.getEditor()); + attr.setId(qu.getId()); + questAttributeRepository.save(attr); + + countResource(qu.getId(), null, attr.getContent()); + onPushEvent(qu, PostUpdateEvent.ACTION_PUBLISH); + return qu.getId(); + } + }finally { + ResourceLock.giveUpAtomicInteger(key); + } + } + + private void onPushEvent(Quest quest, int action) { + PostUpdateEvent event = new PostUpdateEvent(System.currentTimeMillis()); + event.setPostId(quest.getId()); + event.setUserId(quest.getAuthorId()); + event.setAction(action); + applicationContext.publishEvent(event); + } + + private void countResource(Long questId, String originContent, String newContent){ + if (StringUtils.isEmpty(originContent)){ + originContent = ""; + } + if (StringUtils.isEmpty(newContent)){ + newContent = ""; + } + + Set exists = extractImageMd5(originContent); + Set news = extractImageMd5(newContent); + + List adds = ListUtils.removeAll(news, exists); + List deleteds = ListUtils.removeAll(exists, news); + + if (adds.size() > 0) { + List resources = resourceRepository.findByMd5In(adds); + + List qrs = resources.stream().map(n -> { + QuestResource qr = new QuestResource(); + qr.setResourceId(n.getId()); + qr.setQuestId(questId); + qr.setPath(n.getPath()); + return qr; + }).collect(Collectors.toList()); + questResourceRepository.saveAll(qrs); + + resourceRepository.updateAmount(adds, 1); + } + + if (deleteds.size() > 0) { + List resources = resourceRepository.findByMd5In(deleteds); + List rids = resources.stream().map(Resource::getId).collect(Collectors.toList()); + questResourceRepository.deleteByQuestIdAndResourceIdIn(questId, rids); + resourceRepository.updateAmount(deleteds, -1); + } + } + + private Set extractImageMd5(String text) { +// Pattern pattern = Pattern.compile("(?<=/_signature/)[^/]+?jpg"); + + Set md5s = new HashSet<>(); + + Matcher originMatcher = pattern.matcher(text); + while (originMatcher.find()) { + String key = originMatcher.group(); +// md5s.add(key.substring(0, key.lastIndexOf("."))); + md5s.add(key); + } + + return md5s; + } + + /** + * 截取问题内容 + * @param text + * @return + */ + private String trimSummary(String editor, final String text){ + if (Consts.EDITOR_MARKDOWN.endsWith(editor)) { + return PreviewTextUtils.getText(MarkdownUtils.renderMarkdown(text), 126); + } else { + return PreviewTextUtils.getText(text, 126); + } + } + + @Override + public QuestVO get(long id) { + Optional qu = questRepository.findById(id); + if (qu.isPresent()) { + QuestVO d = BeanMapUtils.copy(qu.get()); + + d.setAuthor(userService.get(d.getAuthorId())); + + QuestAttribute attr = questAttributeRepository.findById(d.getId()).get(); + d.setContent(attr.getContent()); + d.setEditor(attr.getEditor()); + return d; + } + return null; + } + + /** + * 更新问题方法 + * @param q + */ + @Override + @Transactional(rollbackFor = Throwable.class) + public void update(QuestVO q) { + Optional optional = questRepository.findById(q.getId()); + + if (optional.isPresent()) { + String key = ResourceLock.getQuestKey(q.getId()); + AtomicInteger lock = ResourceLock.getAtomicInteger(key); + try { + synchronized (lock){ + Quest qu = optional.get(); + qu.setTitle(q.getTitle());//标题 + qu.setStatus(q.getStatus()); + + // 处理摘要 + if (StringUtils.isBlank(qu.getSummary())) { + qu.setSummary(trimSummary(q.getEditor(), q.getContent())); + } else { + qu.setSummary(q.getSummary()); + } + + + // 保存扩展 + Optional attributeOptional = questAttributeRepository.findById(qu.getId()); + String originContent = ""; + if (attributeOptional.isPresent()){ + originContent = attributeOptional.get().getContent(); + } + QuestAttribute attr = new QuestAttribute(); + attr.setContent(q.getContent()); + attr.setEditor(q.getEditor()); + attr.setId(qu.getId()); + questAttributeRepository.save(attr); + + countResource(qu.getId(), originContent, q.getContent()); + } + }finally { + ResourceLock.giveUpAtomicInteger(key); + } + } + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void updateFeatured(long id, int featured) { + Quest qu = questRepository.findById(id).get(); + int status = Consts.FEATURED_ACTIVE == featured ? Consts.FEATURED_ACTIVE: Consts.FEATURED_DEFAULT; + qu.setFeatured(status); + questRepository.save(qu); + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void updateWeight(long id, int weighted) { + Quest qu = questRepository.findById(id).get(); + + int max = Consts.ZERO; + if (Consts.FEATURED_ACTIVE == weighted) { + max = questRepository.maxWeight() + 1; + } + qu.setWeight(max); + questRepository.save(qu); + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void delete(long id, long authorId) { + Quest qu = questRepository.findById(id).get(); + // 判断文章是否属于当前登录用户 + Assert.isTrue(qu.getAuthorId() == authorId, "认证失败"); + + String key = ResourceLock.getQuestKey(qu.getId()); + AtomicInteger lock = ResourceLock.getAtomicInteger(key); + try { + synchronized (lock){ + questRepository.deleteById(id); + questAttributeRepository.deleteById(id); + cleanResource(qu.getId()); + onPushEvent(qu, PostUpdateEvent.ACTION_DELETE); + } + }finally { + ResourceLock.giveUpAtomicInteger(key); + } + } + + private void cleanResource(long questId) { + List list = questResourceRepository.findByQuestId(questId); + if (null == list || list.isEmpty()) { + return; + } + List rids = list.stream().map(QuestResource::getResourceId).collect(Collectors.toList()); + resourceRepository.updateAmountByIds(rids, -1); + questResourceRepository.deleteByQuestId(questId); + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void delete(Collection ids) { + if (CollectionUtils.isNotEmpty(ids)) { + List list = questRepository.findAllById(ids); + list.forEach(qu -> { + String key = ResourceLock.getQuestKey(qu.getId()); + AtomicInteger lock = ResourceLock.getAtomicInteger(key); + try { + synchronized (lock){ + questRepository.delete(qu); + questAttributeRepository.deleteById(qu.getId()); + cleanResource(qu.getId()); + onPushEvent(qu, PostUpdateEvent.ACTION_DELETE); + } + }finally { + ResourceLock.giveUpAtomicInteger(key); + } + }); + } + } + + @Override + @Transactional(rollbackFor = Throwable.class) + public void identityViews(long id) { + // 不清理缓存, 等待文章缓存自动过期 + questRepository.updateViews(id, Consts.IDENTITY_STEP); + } + + @Override + @Transactional + public void identityAnswers(long id) { + questRepository.updateAnswers(id, Consts.IDENTITY_STEP); + } + + @Override + @PostStatusFilter + public long count() { + return questRepository.count(); + } + + @Override + public List findAll(int status) { + Sort sort = Sort.by(Sort.Direction.DESC, "weight", "id"); + List list; + if (status > Consts.IGNORE) { + list = questRepository.findAllByStatus(status, sort); + } else { + list = questRepository.findAll(sort); + } + return list; + } + + @Override + public Quest getById(long id) { + return questRepository.findById(id).get(); + } +} diff --git a/src/main/java/com/mtons/mblog/modules/template/DirectiveHandler.java b/src/main/java/com/mtons/mblog/modules/template/DirectiveHandler.java index 4d8665a03ed46471f6bf03fe8afd018129d99a06..bdafa2a9bfe515396effe31657a894282d76af7c 100644 --- a/src/main/java/com/mtons/mblog/modules/template/DirectiveHandler.java +++ b/src/main/java/com/mtons/mblog/modules/template/DirectiveHandler.java @@ -102,7 +102,8 @@ public class DirectiveHandler { public String getString(String name, String defaultValue) throws Exception { String result = getString(name); - return null == result ? defaultValue : result; + String rs = null == result ? defaultValue : result; + return rs; } public Integer getInteger(String name, int defaultValue) throws Exception { diff --git a/src/main/java/com/mtons/mblog/modules/template/directive/AnswerContentsDirective.java b/src/main/java/com/mtons/mblog/modules/template/directive/AnswerContentsDirective.java new file mode 100644 index 0000000000000000000000000000000000000000..a62cec7121fad31f3de1a2c8a791e3c8d2bd86c3 --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/template/directive/AnswerContentsDirective.java @@ -0,0 +1,54 @@ +package com.mtons.mblog.modules.template.directive; + +import com.mtons.mblog.base.lang.Consts; +import com.mtons.mblog.base.utils.BeanMapUtils; +import com.mtons.mblog.modules.data.AnswerVO; +import com.mtons.mblog.modules.data.PostVO; +import com.mtons.mblog.modules.entity.Answer; +import com.mtons.mblog.modules.entity.Channel; +import com.mtons.mblog.modules.entity.Quest; +import com.mtons.mblog.modules.service.AnswerService; +import com.mtons.mblog.modules.service.QuestService; +import com.mtons.mblog.modules.template.DirectiveHandler; +import com.mtons.mblog.modules.template.TemplateDirective; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Component +public class AnswerContentsDirective extends TemplateDirective { + @Autowired + private AnswerService answerService; + @Autowired + private QuestService questService; + + @Override + public String getName() { + return "answerContents"; + } + + @Override + public void execute(DirectiveHandler handler) throws Exception { + Long questId = handler.getLong("questId", 0); + String order = handler.getString("order", Consts.order.NEWEST); + + Set excludeQuestIds = new HashSet<>(); + + if (questId <= 0) { + List quests = questService.findAll(Consts.STATUS_CLOSED); + if (quests != null) { + quests.forEach((q) -> excludeQuestIds.add(q.getId())); + } + } + + Pageable pageable = wrapPageable(handler, Sort.by(Sort.Direction.DESC, BeanMapUtils.postOrder(order))); + Page result = answerService.paging(pageable, questId, excludeQuestIds); + handler.put(RESULTS, result).render(); + } +} diff --git a/src/main/java/com/mtons/mblog/modules/template/directive/QuestContentsDirective.java b/src/main/java/com/mtons/mblog/modules/template/directive/QuestContentsDirective.java new file mode 100644 index 0000000000000000000000000000000000000000..9c3eea981a5722742af188fa678f0b26570742ca --- /dev/null +++ b/src/main/java/com/mtons/mblog/modules/template/directive/QuestContentsDirective.java @@ -0,0 +1,35 @@ +package com.mtons.mblog.modules.template.directive; + +import com.mtons.mblog.base.lang.Consts; +import com.mtons.mblog.base.utils.BeanMapUtils; +import com.mtons.mblog.modules.data.PostVO; +import com.mtons.mblog.modules.data.QuestVO; +import com.mtons.mblog.modules.service.QuestService; +import com.mtons.mblog.modules.template.DirectiveHandler; +import com.mtons.mblog.modules.template.TemplateDirective; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; + +@Component +public class QuestContentsDirective extends TemplateDirective { + + @Autowired + private QuestService questService; + + @Override + public String getName() { + return "questContents"; + } + + @Override + public void execute(DirectiveHandler handler) throws Exception { + String order = handler.getString("order", Consts.order.NEWEST); + + Pageable pageable = wrapPageable(handler, Sort.by(Sort.Direction.DESC, BeanMapUtils.postOrder(order))); + Page result = questService.paging(pageable); + handler.put(RESULTS, result).render(); + } +} diff --git a/src/main/java/com/mtons/mblog/modules/template/directive/SidebarDirective.java b/src/main/java/com/mtons/mblog/modules/template/directive/SidebarDirective.java index d0a374bc15452fd026029965193bf136b0c8c855..6274157a742cd56b8e9dbebf287e05a4f3cd35f8 100644 --- a/src/main/java/com/mtons/mblog/modules/template/directive/SidebarDirective.java +++ b/src/main/java/com/mtons/mblog/modules/template/directive/SidebarDirective.java @@ -1,7 +1,9 @@ package com.mtons.mblog.modules.template.directive; +import com.mtons.mblog.modules.service.AnswerService; import com.mtons.mblog.modules.service.CommentService; import com.mtons.mblog.modules.service.PostService; +import com.mtons.mblog.modules.service.QuestService; import com.mtons.mblog.modules.template.DirectiveHandler; import com.mtons.mblog.modules.template.TemplateDirective; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +20,10 @@ public class SidebarDirective extends TemplateDirective { private PostService postService; @Autowired private CommentService commentService; + @Autowired + private QuestService questService; + @Autowired + private AnswerService answerService; @Override public String getName() { @@ -27,17 +33,29 @@ public class SidebarDirective extends TemplateDirective { @Override public void execute(DirectiveHandler handler) throws Exception { int size = handler.getInteger("size", 6); + long questId = handler.getLong("questId", 0); + long excludeAnswerId = handler.getLong("excludeId", 0); String method = handler.getString("method", "post_latests"); switch (method) { case "latest_posts": handler.put(RESULTS, postService.findLatestPosts(size)); break; - case "hottest_posts": + case "" + + "hottest_posts": handler.put(RESULTS, postService.findHottestPosts(size)); break; case "latest_comments": handler.put(RESULTS, commentService.findLatestComments(size)); break; + case "hottest_quests": + handler.put(RESULTS, questService.findHottestQuests(size)); + break; + case "latest_quests": + handler.put(RESULTS, questService.findLatestQuests(size)); + break; + case "hottest_answers": + handler.put(RESULTS, answerService.findHottestAnswerByQustId(questId, size, excludeAnswerId)); + break; } handler.render(); } diff --git a/src/main/java/com/mtons/mblog/web/controller/site/IndexController.java b/src/main/java/com/mtons/mblog/web/controller/site/IndexController.java index e83db9722cdd87f0a6afbd71deee67ea9700bb78..f0a45e78e4f2f054d74a3e167578a0ba0b2171ce 100644 --- a/src/main/java/com/mtons/mblog/web/controller/site/IndexController.java +++ b/src/main/java/com/mtons/mblog/web/controller/site/IndexController.java @@ -10,6 +10,7 @@ package com.mtons.mblog.web.controller.site; import javax.servlet.http.HttpServletRequest; +import javax.swing.text.View; import com.mtons.mblog.base.lang.Consts; import org.springframework.stereotype.Controller; diff --git a/src/main/java/com/mtons/mblog/web/controller/site/Views.java b/src/main/java/com/mtons/mblog/web/controller/site/Views.java index 79fc101fa7cc21d8b8d1123e393da16487db4a39..88abd45734ce9bc4b4b9c5bcb6498aad3a243a49 100644 --- a/src/main/java/com/mtons/mblog/web/controller/site/Views.java +++ b/src/main/java/com/mtons/mblog/web/controller/site/Views.java @@ -112,6 +112,15 @@ public interface Views { */ String POST_VIEW = "/channel/view"; + /** + * 问答 + */ + String QUESTION_INDEX = "/questionAnswer/index"; + + String QUEST_VIEW = "/questionAnswer/view"; + + String ANSWER_VIEW = "/questionAnswer/answer/view"; + String REDIRECT_USER_HOME = "redirect:/users/%d"; String REDIRECT_INDEX = "redirect:/index"; } diff --git a/src/main/java/com/mtons/mblog/web/controller/site/posts/UploadController.java b/src/main/java/com/mtons/mblog/web/controller/site/posts/UploadController.java index d69af378416f4b7f6fdc18edfd9d7987be96d6b5..839b460eda628fb23e93bd5957324f5da95c7963 100644 --- a/src/main/java/com/mtons/mblog/web/controller/site/posts/UploadController.java +++ b/src/main/java/com/mtons/mblog/web/controller/site/posts/UploadController.java @@ -47,6 +47,59 @@ public class UploadController extends BaseController { errorInfo.put("UNKNOWN", "未知错误"); } + @PostMapping("/uploadFile") + @ResponseBody + public UploadResult uploadFile(@RequestParam(value = "file", required = false) MultipartFile file, + HttpServletRequest request) throws IOException { + UploadResult result = new UploadResult(); + String crop = request.getParameter("crop"); + int size = ServletRequestUtils.getIntParameter(request, "size", siteOptions.getIntegerValue(Consts.STORAGE_MAX_WIDTH)); + + // 检查空 + if (null == file || file.isEmpty()) { + return result.error(errorInfo.get("NOFILE")); + } + + String fileName = file.getOriginalFilename(); + + // 检查类型 + if (!FileKit.checkFilesType(fileName)) { + return result.error(errorInfo.get("TYPE")); + } + + // 检查大小 + String limitSize = siteOptions.getValue(Consts.STORAGE_LIMIT_SIZE); + if (StringUtils.isBlank(limitSize)) { + limitSize = "2"; + } + if (file.getSize() > (Long.parseLong(limitSize) * 1024 * 1024)) { + return result.error(errorInfo.get("SIZE")); + } + + // 保存图片 + try { + String path; + if (StringUtils.isNotBlank(crop)) { + Integer[] imageSize = siteOptions.getIntegerArrayValue(crop, Consts.SEPARATOR_X); + int width = ServletRequestUtils.getIntParameter(request, "width", imageSize[0]); + int height = ServletRequestUtils.getIntParameter(request, "height", imageSize[1]); + path = storageFactory.get().storeScale(file, Consts.thumbnailPath, width, height); + } else { + path = storageFactory.get().store(file, Consts.thumbnailPath); + } + result.ok(errorInfo.get("SUCCESS")); + result.setName(fileName); + result.setPath(path); + result.setSize(file.getSize()); + + } catch (Exception e) { + result.error(errorInfo.get("UNKNOWN")); + e.printStackTrace(); + } + + return result; + } + @PostMapping("/upload") @ResponseBody public UploadResult upload(@RequestParam(value = "file", required = false) MultipartFile file, @@ -63,7 +116,7 @@ public class UploadController extends BaseController { String fileName = file.getOriginalFilename(); // 检查类型 - if (!FileKit.checkFileType(fileName)) { + if (!FileKit.checkMediaFilesType(fileName)) { return result.error(errorInfo.get("TYPE")); } diff --git a/src/main/java/com/mtons/mblog/web/controller/site/questionAnswerController.java b/src/main/java/com/mtons/mblog/web/controller/site/questionAnswerController.java new file mode 100644 index 0000000000000000000000000000000000000000..65df8f14baf19bedb3bbb48261f67d983ce1df7e --- /dev/null +++ b/src/main/java/com/mtons/mblog/web/controller/site/questionAnswerController.java @@ -0,0 +1,84 @@ +package com.mtons.mblog.web.controller.site; + +import com.mtons.mblog.base.lang.Consts; +import com.mtons.mblog.base.utils.MarkdownUtils; +import com.mtons.mblog.modules.data.AnswerVO; +import com.mtons.mblog.modules.data.PostVO; +import com.mtons.mblog.modules.data.QuestVO; +import com.mtons.mblog.modules.entity.Answer; +import com.mtons.mblog.modules.entity.Channel; +import com.mtons.mblog.modules.entity.Quest; +import com.mtons.mblog.modules.service.AnswerService; +import com.mtons.mblog.modules.service.ChannelService; +import com.mtons.mblog.modules.service.QuestService; +import com.mtons.mblog.web.controller.BaseController; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.util.Assert; +import org.springframework.web.bind.ServletRequestUtils; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.http.HttpServletRequest; +import javax.swing.text.View; + +/** + * 问答板块的controller + * + * @author Logan + */ +@Controller +public class questionAnswerController extends BaseController { + + @Autowired + private QuestService questService; + @Autowired + private AnswerService answerService; + + @RequestMapping("/quest") + public String questIndex(ModelMap model, HttpServletRequest request) { + String order = ServletRequestUtils.getStringParameter(request, "order", Consts.order.NEWEST); + int pageNo = ServletRequestUtils.getIntParameter(request, "pageNo", 1); + model.put("order", order); + model.put("pageNo", pageNo); + + return view(Views.QUESTION_INDEX); + } + + @RequestMapping("/quest/{id:\\d*}") + public String questView(@PathVariable Long id, ModelMap model) { + QuestVO view = questService.get(id); + + Assert.notNull(view, "该问题已被删除"); + + if ("markdown".endsWith(view.getEditor())) { + QuestVO quest = new QuestVO(); + BeanUtils.copyProperties(view, quest); + quest.setContent(MarkdownUtils.renderMarkdown(view.getContent())); + view = quest; + } + questService.identityViews(id); + model.put("view", view); + return view(Views.QUEST_VIEW); + } + + @RequestMapping("/answer/{answerId:\\d*}") + public String answerView(@PathVariable Long answerId, ModelMap model) { + AnswerVO view = answerService.get(answerId); + + Assert.notNull(view, "该回答已被删除"); + + if ("markdown".endsWith(view.getEditor())) { + AnswerVO answer = new AnswerVO(); + BeanUtils.copyProperties(view, answer); + answer.setContent(MarkdownUtils.renderMarkdown(view.getContent())); + view = answer; + } + answerService.identityViews(answerId); + model.put("view", view); + String url = Views.ANSWER_VIEW; + return view(url); + } +} diff --git a/src/main/java/com/mtons/mblog/web/controller/site/user/SettingsController.java b/src/main/java/com/mtons/mblog/web/controller/site/user/SettingsController.java index 00114cd942b57fbf8dff3c93b9aaa2fbafbe362f..5e51a6928ca91182ae1fd07fb60b8f8e57fcbda9 100644 --- a/src/main/java/com/mtons/mblog/web/controller/site/user/SettingsController.java +++ b/src/main/java/com/mtons/mblog/web/controller/site/user/SettingsController.java @@ -131,7 +131,7 @@ public class SettingsController extends BaseController { String fileName = file.getOriginalFilename(); // 检查类型 - if (!FileKit.checkFileType(fileName)) { + if (!FileKit.checkMediaFilesType(fileName)) { return result.error(UploadController.errorInfo.get("TYPE")); } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 708eb788c4e46f95596983db37ede23585ecd7fa..f1d10c0aad4ba2e89c78ad7a81a31f6e39a60919 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -4,9 +4,9 @@ spring: #continue-on-error: false #sql-script-encoding: utf-8 driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost/db_mblog?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8 + url: jdbc:mysql://139.9.6.103/db_mblog?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root - password: root + password: crh123456 flyway: enabled: true jpa: diff --git a/src/main/resources/application-docker.yml b/src/main/resources/application-docker.yml index 0f1aea80812522de1624c1581fefa3c13d1e33b0..52de9011f6670dd4528b14a1de4645bd26ea21bc 100644 --- a/src/main/resources/application-docker.yml +++ b/src/main/resources/application-docker.yml @@ -6,7 +6,7 @@ spring: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://mysql/db_mblog?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root - password: 12345 + password: crh123456 flyway: enabled: true jpa: diff --git a/src/main/resources/application-h2.yml b/src/main/resources/application-h2.yml index c5304139b7b5861a342512a5ba2a49363cd389c6..bd4cf9e29dd3fbdad102c58f6524e5d0ac4b6b39 100644 --- a/src/main/resources/application-h2.yml +++ b/src/main/resources/application-h2.yml @@ -4,7 +4,7 @@ spring: url: jdbc:h2:file:${site.location}/storage/db_mblog #h2 本地数据库文件 # schema: classpath*:scripts/schema.sql #初始化表中记录数据语句 username: root - password: root + password: crh123456 jpa: database: h2 show-sql: false diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2b0b844443c7921ed0fb841bea1fbad23ced01b3..0427f98a8a3d8f8fae974b8a8d2ac881763b08e0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,6 +11,10 @@ spring: active: @profileActive@ devtools: enabled: true + restart: + enabled: true #设置开启热部署 + additional-paths: src/main/java #重启目录 + exclude: WEB-INF/** cache: type: ehcache ehcache: diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt index 7dfe92ee851c3c6936295efdab04e909ec429734..73cd063bab1c64030004db5e8de8868fb3532be9 100644 --- a/src/main/resources/banner.txt +++ b/src/main/resources/banner.txt @@ -1,13 +1,16 @@ ------------------------------------------------------- - _____ ______ ________ ___ ________ ________ -|\ _ \ _ \|\ __ \|\ \ |\ __ \|\ ____\ -\ \ \\\__\ \ \ \ \|\ /\ \ \ \ \ \|\ \ \ \___| - \ \ \\|__| \ \ \ __ \ \ \ \ \ \\\ \ \ \ ___ - \ \ \ \ \ \ \ \|\ \ \ \____\ \ \\\ \ \ \|\ \ - \ \__\ \ \__\ \_______\ \_______\ \_______\ \_______\ - \|__| \|__|\|_______|\|_______|\|_______|\|_______| ------------------------------------------------------------- +--------------------------------------------------------------------------------------------------------------------------------- +8 8888 ,o888888o. ,o888888o. .8. b. 8 8 8888 8 8 8888 88 8 888888888o +8 8888 . 8888 `88. 8888 `88. .888. 888o. 8 8 8888 8 8 8888 88 8 8888 `88. +8 8888 ,8 8888 `8b ,8 8888 `8. :88888. Y88888o. 8 8 8888 8 8 8888 88 8 8888 `88 +8 8888 88 8888 `8b 88 8888 . `88888. .`Y888888o. 8 8 8888 8 8 8888 88 8 8888 ,88 +8 8888 88 8888 88 88 8888 .8. `88888. 8o. `Y888888o. 8 8 8888 8 8 8888 88 8 8888. ,88' +8 8888 88 8888 88 88 8888 .8`8. `88888. 8`Y8o. `Y88888o8 8 8888 8 8 8888 88 8 8888888888 +8 8888 88 8888 ,8P 88 8888 8888888 .8' `8. `88888. 8 `Y8o. `Y8888 8 8888888888888 8 8888 88 8 8888 `88. +8 8888 `8 8888 ,8P `8 8888 .8'.8' `8. `88888. 8 `Y8o. `Y8 8 8888 8 ` 8888 ,8P 8 8888 88 +8 8888 ` 8888 ,88' 8888 ,88'.888888888. `88888. 8 `Y8o.` 8 8888 8 8888 ,d8P 8 8888 ,88' +8 888888888888 `8888888P' `8888888P' .8' `8. `88888. 8 `Yo 8 8888 8 `Y88888P' 8 888888888P +--------------------------------------------------------------------------------------------------------------------------------- Spring Boot : ${spring-boot.version} -mblog : ${site.version} -mblog storage : ${site.location} ------------------------------------------------------------- \ No newline at end of file +LoganHub : ${site.version} +LoganHub storage : ${site.location} +--------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/main/resources/static/dist/js/modules/editor.js b/src/main/resources/static/dist/js/modules/editor.js index b89d3fe292ea5c6a22d0c7698b265af1ebb0b404..2ca45bb8279ba4d339e2380cc8b1da36bbf6b549 100644 --- a/src/main/resources/static/dist/js/modules/editor.js +++ b/src/main/resources/static/dist/js/modules/editor.js @@ -25,6 +25,57 @@ define(function(require, exports, module) { ], toolbar: "undo redo | formatselect | bold underline blockquote | alignleft aligncenter alignright | " + "forecolor bullist numlist | link unlink | uploadimage codesample removeformat | fullscreen ", + + file_picker_callback: function (callback, value, meta) { + //文件分类 + var filetype='.pdf, .txt, .zip, .rar, .7z, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .mp3, .mp4'; + //后端接收上传文件的地址 + var upurl=_MTONS.BASE_PATH + "/post/uploadFile"; + //为不同插件指定文件类型及后端地址 + // switch(meta.filetype){ + // case 'image': + // filetype='.jpg, .jpeg, .png, .gif'; + // upurl='upimg.php'; + // break; + // case 'media': + // filetype='.mp3, .mp4'; + // upurl='upfile.php'; + // break; + // case 'file': + // default: + // } + //模拟出一个input用于添加本地文件 + var input = document.createElement('input'); + input.setAttribute('type', 'file'); + input.setAttribute('accept', filetype); + input.click(); + input.onchange = function() { + var file = this.files[0]; + + var xhr, formData; + console.log(file.name); + xhr = new XMLHttpRequest(); + xhr.withCredentials = false; + xhr.open('POST', upurl); + xhr.onload = function() { + var json; + console.log(xhr) + if (xhr.status != 200) { + return; + } + json = JSON.parse(xhr.responseText); + if (json.status != 200) { + return; + } + console.log(json.path); + callback(json.path); + }; + formData = new FormData(); + formData.append('file', file, file.name ); + xhr.send(formData); + }; + }, + file_picker_types: 'file image media', menubar: false, statusbar : false, paste_data_images: true, diff --git a/src/main/resources/templates/classic/about.json b/src/main/resources/templates/classic/about.json deleted file mode 100644 index e1caefe173a4509288160e170db2fdfc8a33416f..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/about.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "classic", - "slogan": "经典主题", - "version": "1.0", - "author": "landy", - "website": "http://mtons.com", - "previews": [ - "/theme/classic/dist/images/preview/1.png" - ] -} \ No newline at end of file diff --git a/src/main/resources/templates/classic/auth/forgot.ftl b/src/main/resources/templates/classic/auth/forgot.ftl deleted file mode 100644 index d9034e56d655f8b6831ee4f7f0283c81dec196f4..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/auth/forgot.ftl +++ /dev/null @@ -1,54 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 重置密码 - - - <@layout.put block="contents"> -
-
-
-
-

找回密码

-
-
-
- <@layout.extends name="/inc/action_message.ftl" /> -
-
-
- -
- - - - -
-
-
- - -
-
- - -
-
- - -
- -
-
-
-
-
- - - - \ No newline at end of file diff --git a/src/main/resources/templates/classic/auth/login.ftl b/src/main/resources/templates/classic/auth/login.ftl deleted file mode 100644 index cbd9d90799430dab513a320db010e5cad4c1117b..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/auth/login.ftl +++ /dev/null @@ -1,66 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 登录 - - - <@layout.put block="contents"> -
-
-
-
-

请登录

-
-
-
- <@layout.extends name="/inc/action_message.ftl" /> -
-
-
- - -
-
- - -
-
- - - 忘记密码? - -
-
- -
- <@controls name="register"> -
- <#if site.hasValue("weibo_client_id")> - - 微博帐号登录 - - - <#if site.hasValue("qq_app_id")> - - QQ帐号登录 - - - <#if site.hasValue("github_client_id")> - - Github帐号登录 - - -
- -
-
-
-
-
- - - - diff --git a/src/main/resources/templates/classic/auth/oauth_register.ftl b/src/main/resources/templates/classic/auth/oauth_register.ftl deleted file mode 100644 index beec30b356d8c22f7a6c4e302c736e109fda1ba9..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/auth/oauth_register.ftl +++ /dev/null @@ -1,46 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 注册 - - - <@layout.put block="contents"> -
-
-
-
-

离成功只差一步

-
-
-
- <@layout.extends name="/inc/action_message.ftl" /> -
-
- - - - - - - - - -
- - -
- -
-
-
-
-
- - - - \ No newline at end of file diff --git a/src/main/resources/templates/classic/auth/register.ftl b/src/main/resources/templates/classic/auth/register.ftl deleted file mode 100644 index da19d99df0bf06420ede47cfaa602437497d08e2..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/auth/register.ftl +++ /dev/null @@ -1,59 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 注册 - - <@layout.put block="contents"> -
-
-
-
-

注册

-
-
- <@layout.extends name="/inc/action_message.ftl" /> -
-
-
-
- - -
- <@controls name="register_email_validate"> -
- -
- - - - -
-
-
- - -
- -
- - -
-
- - -
- -
-
-
-
-
- - - - \ No newline at end of file diff --git a/src/main/resources/templates/classic/channel/editing.ftl b/src/main/resources/templates/classic/channel/editing.ftl deleted file mode 100644 index 7fab91650b4d893d8f14149eb60f3a05d95892e4..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/channel/editing.ftl +++ /dev/null @@ -1,77 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 编辑文章 - - - <@layout.put block="contents"> -
- - -
-
-
- <#if view??> - - - - - -
- -
-
- <@layout.extends name="/channel/editor/${editor}.ftl" /> -
-
-
-
-
-
style="background: url(<@resource src=view.thumbnail/>);" > -
- -
-
-
-
-
-
-

发布到

-
-
- -
-
-
-
-

标签(用逗号或空格分隔)

-
-
- -
-
-
-
-
- -
-
-
-
-
-
- - - - diff --git a/src/main/resources/templates/classic/channel/index.ftl b/src/main/resources/templates/classic/channel/index.ftl deleted file mode 100644 index cbc55f7a9e12735304b75e91f65a013209d7928e..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/channel/index.ftl +++ /dev/null @@ -1,40 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - channel.name - - - <@layout.put block="contents"> -
-
- <@contents channelId=channel.id pageNo=pageNo order=order> -
-
    - <@layout.extends name="/inc/posts_item.ftl" /> - <#list results.content as row> - <@posts_item row/> - - <#if results.content?size == 0> -
  • -
    -
    该目录下还没有内容!
    -
    -
  • - -
-
- - -
- <@utils.pager request.requestURI!"", results, 5/> -
- - -
- -
- <@layout.extends name="/inc/right.ftl" /> -
-
- - - diff --git a/src/main/resources/templates/classic/channel/view.ftl b/src/main/resources/templates/classic/channel/view.ftl deleted file mode 100644 index b313c6f745f419983dcc58763e2b0d72668a7cd9..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/channel/view.ftl +++ /dev/null @@ -1,182 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - ${view.title} - ${options['site_name']} - - - <@layout.put block="keywords"> - - - - <@layout.put block="description"> - - - - <@layout.put block="contents"> -
-
- -
-
-

${view.title}

-
- - ${view.author.name} - - ${timeAgo(view.created)} - ⋅ ${view.views} 阅读 -
-
-
- -
-
- ${view.content} -
-
- - - -
- - - <@controls name="comment"> -
-
-

全部评论: 0

-
-
    -
    -
    -
    我有话说: -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - -
    -
    -
    - -
    -
    - - -
    - -
    - - - - - - diff --git a/src/main/resources/templates/classic/dist/images/preview/1.png b/src/main/resources/templates/classic/dist/images/preview/1.png deleted file mode 100644 index f4d20642d1556f7ccba385c49064db173feb1c0d..0000000000000000000000000000000000000000 Binary files a/src/main/resources/templates/classic/dist/images/preview/1.png and /dev/null differ diff --git a/src/main/resources/templates/classic/inc/right.ftl b/src/main/resources/templates/classic/inc/right.ftl deleted file mode 100644 index d4efbdc7c9640e150ea0c57cbdb535f5207481f9..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/inc/right.ftl +++ /dev/null @@ -1,45 +0,0 @@ -
    -
    -

    热门文章

    -
    -
    - <@sidebar method="hottest_posts"> -
      - <#list results as row> -
    • ${row_index + 1}. ${row.title}
    • - -
    - -
    -
    - -
    -
    -

    最新发布

    -
    -
    - <@sidebar method="latest_posts"> -
      - <#list results as row> -
    • ${row_index + 1}. ${row.title}
    • - -
    - -
    -
    -<@controls name="comment"> -
    -
    -

    最新评论

    -
    -
    - <@sidebar method="latest_comments"> - - -
    -
    - \ No newline at end of file diff --git a/src/main/resources/templates/classic/inc/user_sidebar.ftl b/src/main/resources/templates/classic/inc/user_sidebar.ftl deleted file mode 100644 index b67a434a3011af89c89be48b4e6b1336bf103445..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/inc/user_sidebar.ftl +++ /dev/null @@ -1,68 +0,0 @@ - -
      -
    • -
      - <@utils.showAva user "img-circle"/> -
      -
      - ${user.name} -
      -
    • -
    • -
      -
        -
      • ${user.posts}发布
      • -
      • ${user.comments}评论
      • -
      -
      -
    • - <#if owner> -
    • - - 编辑个人资料 - -
    • - -
    - diff --git a/src/main/resources/templates/classic/index.ftl b/src/main/resources/templates/classic/index.ftl deleted file mode 100644 index 721ab70d85158009eae62d1887f21ce73b63e641..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/index.ftl +++ /dev/null @@ -1,63 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - - <@layout.put block="contents"> - <#assign topId = 1 /> - - - <@contents channelId=topId size=3> - <#if results.content?size gt 0> - - - - - - -
    -
    -
    - <@contents pageNo=pageNo> -
      - <@layout.extends name="/inc/posts_item.ftl" /> - <#list results.content as row> - <@posts_item row/> - - <#if results.content?size == 0> -
    • -
      -
      该目录下还没有内容!
      -
      -
    • - -
    - -
    -
    - - <@utils.pager request.requestURI!"", results, 5/> -
    -
    -
    - <@layout.extends name="/inc/right.ftl" /> -
    -
    - - \ No newline at end of file diff --git a/src/main/resources/templates/classic/search.ftl b/src/main/resources/templates/classic/search.ftl deleted file mode 100644 index c0f18efa3d515d0a38d988671e01e982cc4f26e3..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/search.ftl +++ /dev/null @@ -1,39 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 搜索: ${kw} - - - <@layout.put block="contents"> -
    -
    -
    -
      -
    • -
      -
      搜索: ${kw} 共 ${results.totalElements} 个结果.
      -
      -
    • - <@layout.extends name="/inc/posts_item.ftl" /> - <#list results.content as row> - <@posts_item row false/> - - <#if !results?? || results.content?size == 0> -
    • -
      -
      该目录下还没有内容!
      -
      -
    • - -
    -
    -
    - <@utils.pager request.requestURI!"" + "?kw=${kw}", results, 5/> -
    -
    -
    - <@layout.extends name="/inc/right.ftl" /> -
    -
    - - - diff --git a/src/main/resources/templates/classic/settings/avatar.ftl b/src/main/resources/templates/classic/settings/avatar.ftl deleted file mode 100644 index 17bca0ca29f7d50d456f417334d4bfffdcc4596e..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/settings/avatar.ftl +++ /dev/null @@ -1,36 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 修改用户信息 - - - <@layout.put block="contents"> -
    - -
    -
    - <@layout.extends name="/inc/action_message.ftl" /> -
    -
    - -
    -
    - [Example] -
    -
    -
    - - - - \ No newline at end of file diff --git a/src/main/resources/templates/classic/settings/email.ftl b/src/main/resources/templates/classic/settings/email.ftl deleted file mode 100644 index 77d07764c5a038fdcd6b3b9b21791348255769ff..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/settings/email.ftl +++ /dev/null @@ -1,55 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 修改邮箱 - - - <@layout.put block="contents"> -
    - -
    -
    - <@layout.extends name="/inc/action_message.ftl" /> -
    -
    -
    -
    - -
    -
    - - - - -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/src/main/resources/templates/classic/settings/password.ftl b/src/main/resources/templates/classic/settings/password.ftl deleted file mode 100644 index 35ef6e01426c69ef87a3500380b5ed1c0e599236..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/settings/password.ftl +++ /dev/null @@ -1,56 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 修改用户信息 - - - <@layout.put block="contents"> -
    - -
    -
    - <@layout.extends name="/inc/action_message.ftl" /> -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/src/main/resources/templates/classic/settings/profile.ftl b/src/main/resources/templates/classic/settings/profile.ftl deleted file mode 100644 index 96fc1a35f67efb80e914ff26eb0767af6b015414..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/settings/profile.ftl +++ /dev/null @@ -1,50 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 修改用户信息 - - - <@layout.put block="contents"> -
    - -
    -
    - <@layout.extends name="/inc/action_message.ftl" /> -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/src/main/resources/templates/classic/tag/index.ftl b/src/main/resources/templates/classic/tag/index.ftl deleted file mode 100644 index ba2c12b6ea5d167ecdd0e70b12540aebbc98a101..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/tag/index.ftl +++ /dev/null @@ -1,53 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 标签列表 - - - <@layout.put block="contents"> -
    -
    -
    -
    - <#list results.content as row> - <#assign post = row.post /> -
    -

    - ${row.name} - ${row.posts} -

    - <#if post?if_exists> -
    -
    - <@utils.showAva post.author "media-object"/> -
    - -
    - -
    - - <#if results.content?size == 0> -
    -
    该目录下还没有内容!
    -
    - -
    -
    - - -
    - <@utils.pager request.requestURI!"", results, 5/> -
    -
    - -
    - <@layout.extends name="/inc/right.ftl" /> -
    - -
    - - - diff --git a/src/main/resources/templates/classic/user/method_comments.ftl b/src/main/resources/templates/classic/user/method_comments.ftl deleted file mode 100644 index f63aff0237de58f88a92d9d6c5a0d98bf36c32f2..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/user/method_comments.ftl +++ /dev/null @@ -1,84 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - ${user.name}的评论 - - - <@layout.put block="contents"> -
    -
    - <@layout.extends name="/inc/user_sidebar.ftl" /> -
    -
    -
    -
    发表的评论
    - <@user_comments userId=user.id pageNo=pageNo> -
    -
      - <#list results.content as row> -
    • - <#if row.post??> - ${row.post.title} - <#else> - 文章已删除 - - - ${timeAgo(row.created)} - - - - -
      -

      ${row.content}

      -
      -
    • - - - <#if results.content?size == 0> -
    • -
      -
      该目录下还没有内容!
      -
      -
    • - -
    -
    - - -
    -
    -
    - - - - - diff --git a/src/main/resources/templates/classic/user/method_favorites.ftl b/src/main/resources/templates/classic/user/method_favorites.ftl deleted file mode 100644 index f5d11481a0ecaa045415b0be0082b5cda7143482..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/user/method_favorites.ftl +++ /dev/null @@ -1,81 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - ${user.name}的收藏 - - - <@layout.put block="contents"> -
    -
    - <@layout.extends name="/inc/user_sidebar.ftl" /> -
    -
    -
    -
    收藏的文章
    - <@user_favorites userId=user.id pageNo=pageNo> -
    -
      - <#list results.content as row> - <#assign target = row.post /> -
    • - <#if target??> - ${target.title} - <#else> - 文章已删除 - - - ${timeAgo(row.created)} - - - -
    • - - - <#if results.content?size == 0> -
    • -
      -
      该目录下还没有内容!
      -
      -
    • - -
    -
    - - -
    -
    -
    - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/classic/user/method_messages.ftl b/src/main/resources/templates/classic/user/method_messages.ftl deleted file mode 100644 index ff549873696807fd1fae338709d95c4b85baa5bc..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/user/method_messages.ftl +++ /dev/null @@ -1,63 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 通知 - - - <@layout.put block="contents"> -
    -
    - <@layout.extends name="/inc/user_sidebar.ftl" /> -
    -
    -
    -
    通知列表
    - <@user_messages userId=user.id pageNo=pageNo> -
    -
      - <#list results.content as row> -
    • - -
      - <@utils.showAva row.from "media-object img-thumbnail avatar avatar-middle"/> -
      -
      -
      - <#----> - ${row.from.name} - - <#if (row.event == 1)> - 收藏了你的文章 - ${row.post.title} - <#elseif (row.event == 3)> - 评论了你的文章 - 点击查看详情 - <#elseif (row.event == 4)> - 回复了你的评论 - 点击查看详情 - - -
      -
      -
    • - - - <#if results.content?size == 0> -
    • -
      -
      该目录下还没有内容!
      -
      -
    • - -
    -
    - - -
    -
    -
    - - - \ No newline at end of file diff --git a/src/main/resources/templates/classic/user/method_posts.ftl b/src/main/resources/templates/classic/user/method_posts.ftl deleted file mode 100644 index 42b43c18bacf85e74d912a64d452e69a6ef5083d..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/classic/user/method_posts.ftl +++ /dev/null @@ -1,89 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - ${user.name}的文章 - - - <@layout.put block="contents"> -
    -
    - <@layout.extends name="/inc/user_sidebar.ftl" /> -
    -
    -
    -
    发表的文章
    - <@user_contents userId=user.id pageNo=pageNo> -
    -
      - <#list results.content as row> -
    • - ${row.title} - - ${row.favors} 点赞 - - ${row.comments} 回复 - - ${timeAgo(row.created)} - - - -
    • - - - <#if results.content?size == 0> -
    • -
      -
      该目录下还没有内容!
      -
      -
    • - -
    -
    - - -
    -
    -
    - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/about.json b/src/main/resources/templates/default/about.json deleted file mode 100644 index d4e91b59316b5a6d8c051f860e4d297c08a42113..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/about.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "default", - "slogan": "默认主题", - "version": "1.0", - "author": "landy", - "website": "http://mtons.com", - "previews": [ - "/theme/default/dist/images/preview/1.png" - ] -} \ No newline at end of file diff --git a/src/main/resources/templates/default/auth/forgot.ftl b/src/main/resources/templates/default/auth/forgot.ftl deleted file mode 100644 index 0a58f04587d80842eb8c564b1f851179a7781b9c..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/auth/forgot.ftl +++ /dev/null @@ -1,55 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 重置密码 - - - <@layout.put block="contents"> -
    -
    -
    -
    -

    找回密码

    -
    -
    -
    - <#include "/default/inc/action_message.ftl"/> -
    -
    -
    - -
    - - - - -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    - - -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/auth/login.ftl b/src/main/resources/templates/default/auth/login.ftl deleted file mode 100644 index 8a1614595206208edb11dadeaab8cfe0e5a2dc1e..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/auth/login.ftl +++ /dev/null @@ -1,63 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 重置密码 - - - <@layout.put block="contents"> -
    -
    -
    -
    -

    请登录

    -
    -
    -
    <#include "/default/inc/action_message.ftl"/>
    -
    -
    - - -
    -
    - - -
    -
    - - - 忘记密码? - -
    -
    - -
    - <@controls name="register"> -
    - <#if site.hasValue("weibo_client_id")> - - 微博帐号登录 - - - <#if site.hasValue("qq_app_id")> - - QQ帐号登录 - - - <#if site.hasValue("github_client_id")> - - Github帐号登录 - - -
    - -
    -
    -
    -
    -
    - - - diff --git a/src/main/resources/templates/default/auth/oauth_register.ftl b/src/main/resources/templates/default/auth/oauth_register.ftl deleted file mode 100644 index c7a5318a8025121c90e5159eac3d2a81fafb583f..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/auth/oauth_register.ftl +++ /dev/null @@ -1,44 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 注册 - - - <@layout.put block="contents"> -
    -
    -
    -
    -

    离成功只差一步

    -
    -
    -
    <#include "/default/inc/action_message.ftl"/>
    -
    - - - - - - - - - -
    - - -
    - -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/auth/register.ftl b/src/main/resources/templates/default/auth/register.ftl deleted file mode 100644 index 353955ccdcc23ff85d68ef55446bef0f6e39a3ae..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/auth/register.ftl +++ /dev/null @@ -1,59 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 注册 - - <@layout.put block="contents"> -
    -
    -
    -
    -

    注册

    -
    -
    - <#include "/default/inc/action_message.ftl"/> -
    -
    -
    -
    - - -
    - <@controls name="register_email_validate"> -
    - -
    - - - - -
    -
    -
    - - -
    - -
    - - -
    -
    - - -
    - -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/channel/editing.ftl b/src/main/resources/templates/default/channel/editing.ftl deleted file mode 100644 index c177d21580af328bac33c8ab8b13db792c85e9ff..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/channel/editing.ftl +++ /dev/null @@ -1,72 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 编辑文章 - - - <@layout.put block="contents"> -
    - - -
    -
    -
    - <#if view??> - - - - - -
    - -
    -
    - -
    -
    - <#include "/default/channel/editor/${editor}.ftl"/> -
    -
    -
    -
    -
    -
    style="background: url(<@resource src=view.thumbnail/>);" > -
    - -
    -
    -
    -
    -
    -
    -

    标签(用逗号或空格分隔)

    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    - - - - diff --git a/src/main/resources/templates/default/channel/editor/markdown.ftl b/src/main/resources/templates/default/channel/editor/markdown.ftl deleted file mode 100644 index 9c3a1734cd08c1a51bc29ed3b690441eec399693..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/channel/editor/markdown.ftl +++ /dev/null @@ -1,59 +0,0 @@ -
    -
    - - - | - - - - - - - - - | - - - - | - -
    -
    -
    - -
    -
    -
    -
    -
    - diff --git a/src/main/resources/templates/default/channel/editor/tinymce.ftl b/src/main/resources/templates/default/channel/editor/tinymce.ftl deleted file mode 100644 index b83df4a8ff52e9883327cbc4c05291bbded2d75d..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/channel/editor/tinymce.ftl +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/main/resources/templates/default/channel/index.ftl b/src/main/resources/templates/default/channel/index.ftl deleted file mode 100644 index 6510360d24496c6eec59bf7d738a07aef2391b11..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/channel/index.ftl +++ /dev/null @@ -1,75 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - channel.name - - - <@layout.put block="contents"> -
    -
    -
    - - <@contents channelId=channel.id pageNo=pageNo order=order> - -
    - - -
    - - - -
    -
    -
    - <#include "/default/inc/right.ftl" /> -
    -
    - - diff --git a/src/main/resources/templates/default/channel/view.ftl b/src/main/resources/templates/default/channel/view.ftl deleted file mode 100644 index f6c27fa8d7a63a4f68acbf9e1117dc4e47342535..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/channel/view.ftl +++ /dev/null @@ -1,178 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - ${view.title} - ${options['site_name']} - - - <@layout.put block="keywords"> - - - - <@layout.put block="description"> - - - - <@layout.put block="contents"> -
    -
    - -
    -
    -

    ${view.title}

    -
    - - ${view.author.name} - - ${timeAgo(view.created)} - ⋅ - ${view.views} 阅读 -
    -
    -
    - -
    -
    - ${view.content} -
    -
    - - -
    - - - <@controls name="comment"> -
    -
    -

    全部评论: 0

    -
    -
      -
      -
      -
      我有话说: -
      -
      -
      - - -
      -
      -
      -
      -
      -
      - -
      -
      -
      - -
      -
      - - -
      - -
      - - - - - - diff --git a/src/main/resources/templates/default/dist/css/style.css b/src/main/resources/templates/default/dist/css/style.css deleted file mode 100644 index cf7661a9df1a05e93552a7fb86e53895668a8e4b..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/dist/css/style.css +++ /dev/null @@ -1,1833 +0,0 @@ -body { - margin: 0; - padding-top: 50px; - background: #f1f1f1; - font-family: 'Helvetica Neue', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', 'Microsoft Yahei', sans-serif; - -webkit-font-smoothing: antialiased; -} - -ul, ol, h1, h2, h3 { - list-style: none; -} - -a { - color: #333; - text-decoration: none; -} - -a:hover, a:focus, a:active { - color: #03a9f4; - text-decoration: none; -} - -blockquote { - display: block; - border-left: 5px solid #d0e5f2; - padding: 0 0 0 10px; - margin: 0; - line-height: 1.4; - font-size: 100%; -} - -p { - font-size: 14px; - color: #566573; -} - -img { - vertical-align: middle; -} - -abbr[data-original-title], abbr[title] { - cursor: none; - border-bottom: none; -} - -.mt15 { - margin-top: 15px; -} - -.mt20 { - margin-top: 20px; -} - -.padding-md { - padding: 15px; -} - -.padding-sm { - padding: 10px; -} - -.remove-padding-left { - padding-left: 0px !important; -} - -.remove-padding-horizontal { - padding-bottom: 0px; - padding-top: 0px; -} - -.background-white { - background-color: #fff; -} - -.meta, .operate { - color: #888; - font-size: 14px; -} - -.meta a, .operate a { - padding: 5px; - color: #888; - text-decoration: none; -} - -.avatar { - border-radius: 50%; -} - -.avatar-topnav { - width: 30px; - height: 30px; - margin-right: 5px; - margin-top: -5px; - border: 1px solid white; - border-radius: 50%; -} - -.avatar-circle { - border-radius: 50% !important; - border: 1px solid #ccc; -} - -.avatar-extral-small { - width: 36px; -} - -.avatar-112 { - width: 112px; - height: 112px; -} - -.avatar-66 { - width: 66px; - height: 66px; -} - -.avatar-96 { - width: 96px; - height: 96px; -} - -.more-excellent-topic-link { - color: #999; - font-size: 13px; -} - -.fl { - float: left; -} - -.fr { - float: right; -} - -/* Override bootstrap --------------------------------------------------- */ -.btn-primary { - color: #fff; - background-color: #03a9f4; - border-color: #03a9f4; -} - -.btn-primary:hover, .btn-primary.active, .btn-primary:focus { - color: #fff; - background-color: #17b2f9; - border-color: #17b2f9; -} - -.btn-default { - border-color: #ebebeb; -} - -.btn-default:hover, .btn-default.active, .btn-default:focus { - background-color: #fff; -} - -.btn-inverted { - color: #03a9f4; - background-color: transparent; - border: 1px solid #03a9f4; -} - -.btn-success { - color: #fff; - background-color: #62c162; - border-color: #62c162; -} - -.btn-success:hover, .btn-success.active, .btn-success:focus { - color: #fff; - background-color: #5cb85c; - border-color: #5cb85c; -} - -.pagination { - margin: 15px 0; -} - -.pagination > .active > a, .pagination > .active > a:focus, .pagination > .active > a:hover, .pagination > .active > span, .pagination > .active > span:focus, .pagination > .active > span:hover { - background-color: #6fbef7; - border-color: #6fbef7; -} - -.pagination > li > a, .pagination > li > span { - color: #999; - background-color: #fafafa; - border-color: #e6e6e6; -} - -.pagination > li:first-child > a, .pagination > li:first-child > span { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; -} - -.pagination > li:last-child > a, .pagination > li:last-child > span { - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; -} - -.panel { - border-radius: 2px; - border-color: #f0f0f0; -} - -.panel .panel-heading { - border-bottom: 1px solid #f0f0f0; - background-color: #fff; -} - -.panel .panel-footer { - border-top: none; - background-color: #fcfcfc; -} - -.nav-pills > li.active > a, .nav-pills > li.active > a:focus, .nav-pills > li.active > a:hover { - background-color: #03a9f4; -} - -.breadcrumb > .active a { - color: #337ab7; -} - -.help-block { - margin-bottom: 5px; -} - -/* HEAD --------------------------------------------------- */ -.site-header { - position: fixed; - top: 0; - z-index: 99; - width: 100%; - min-height: 50px; - border-bottom: 1px solid #f0f0f0; - background: #171717; - opacity: .97; -} - -.site-header .navbar-brand { - padding: 9px 0; -} - -.site-header .navbar-brand img { - max-height: 32px; -} - -.site-header .navbar { - border: 0; - margin-bottom: 0; -} - -.site-header a { - color: #dbdbdb; -} - -.site-header .navbar-nav { - margin-left: 25px; -} - -.site-header .navbar-nav > li > a { - font-size: 14px; - padding-top: 15px; - padding-bottom: 15px; -} - -.site-header .navbar-nav > li > a:focus, -.site-header .navbar-nav > li > a:hover, -.site-header .navbar-button > li > a:focus, -.site-header .navbar-button > li > a:hover { - color: #fff; - background-color: transparent; -} - -.site-header .navbar-nav > li.active > a { - color: #fff; -} - -.site-header .navbar-button { - margin: 0; - text-align: right; -} - -.site-header .navbar-button > li { - margin: 0; - padding: 0; - vertical-align: top; -} - -.site-header .navbar-button > li a.btn { - margin: 10px 0 10px 10px; -} - -.site-header .navbar-button > li a.plus { - display: inline-block; - padding: 7px 10px; - margin: 10px 0 10px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} - -.site-header .navbar-button > li > a.login, .site-header .navbar-button > li > a.signup { - min-width: 70px; - background-color: #333; - border: 0 solid transparent; -} - -.site-header .navbar-button > li > a.login:hover, .site-header .navbar-button > li > a.signup:hover { - color: #171717!important; - background: #fff!important; -} - -.site-header .navbar-button > li > a.user { - padding: 10px 10px; - line-height: 30px; - position: relative; - display: block; -} - -.site-header .navbar-button > li > a.user img { - width: 30px; - height: 30px; - border: 0 solid transparent; - border-radius: 50%; - margin-right: 5px; -} - -.site-header .navbar .navbar-toggle { - margin-right: 0; - padding-right: 0; - margin-top: 12px; -} - -.site-header .navbar .navbar-toggle .icon-bar { - background-color: #6e6e6e; -} - -.site-header .navbar-form { - position: relative; - padding: 0px 20px; -} - -.site-header .navbar-form .form-control { - padding-left: 30px; - font-size: 14px; - line-height: 1em; - background-color: #333; - color: #ccc; - border: none; - border-radius: 0; -} - -.site-header .navbar-form .mac-style { - width: 180px; - transition: width 1s ease-in-out; -} - -.site-header .navbar-form .search-input { - border-radius: 3px; - box-shadow: none; - transition: all 0.15s ease-in 0s; -} - -.site-header .navbar-form .search-btn { - position: absolute; - left: 20px; - top: 6px; - font-size: 14px; - color: #ccc; - background-color: transparent; - border: none; - outline: none; -} - -.site-header .navbar-form .search-btn:hover { - color: #969696; -} - -.site-header .navbar-form .mac-style:focus { - width: 200px; - background-color: #333; -} - -.headroom { - position: fixed; - z-index: 99; - top: 0; - left: 0; - right: 0; - -webkit-transition: .5s; - -o-transition: .5s; - -moz-transition: .5s; - -ms-transition: .5s; - transition: transform 0.5s ease-in-out; - -webkit-transform: translateY(0px); - -o-transform: translateY(0px); - -moz-transform: translateY(0px); - -ms-transform: translateY(0px); - transform: translateY(0px); -} - -.headroom--unpinned { - top: -65px; - -webkit-transition: .5s; - -o-transition: .5s; - -moz-transition: .5s; - -ms-transition: .5s; - transition: .5s; - -webkit-transform: translateY(-65px); - -o-transform: translateY(-65px); - -moz-transform: translateY(-65px); - -ms-transform: translateY(-65px); - transform: translateY(-65px); -} - -.headroom--pinned { - top: 0; - -webkit-transition: .5s; - -o-transition: .5s; - -moz-transition: .5s; - -ms-transition: .5s; - transition: .5s; -} - -/* 滚动条 --------------------------------------------------- */ -/*滚动条宽度*/ -::-webkit-scrollbar { - width: 5px; -} - -/* 轨道样式 */ -::-webkit-scrollbar-track { - -} - -/* Handle样式 */ -::-webkit-scrollbar-thumb { - border-radius: 10px; - background: rgba(0, 0, 0, 0.2); -} - -/*当前窗口未激活的情况下*/ -::-webkit-scrollbar-thumb:window-inactive { - background: rgba(0, 0, 0, 0.1); -} - -/*hover到滚动条上*/ -::-webkit-scrollbar-thumb:vertical:hover { - background-color: rgba(0, 0, 0, 0.3); -} - -/*滚动条按下*/ -::-webkit-scrollbar-thumb:vertical:active { - background-color: rgba(0, 0, 0, 0.7); -} - -/* Layout --------------------------------------------------- */ -.wrap { - position: relative; - /*background-color: #fafafa;*/ - margin-top: 30px; - margin-bottom: 30px; - min-height: 750px; -} - -.side-left { -} - -.side-right { - padding: 0 15px 0 15px; - margin-left: -15px; -} - -.wrap .avatar { - padding: 3px; -} - -/* CUSTOMIZE THE CAROUSEL --------------------------------------------------- */ -.section { - margin-bottom: 15px; -} - -.section .section-title { - color: #aaaaaa; - font-weight: 200; - border-bottom: 0px; - text-transform: uppercase; - transition: color 0.3s linear; -} - -.section .desc { - margin-top: 10px; - color: #999; -} - -.search-banner { - max-width: 500px; - margin: 30px auto 50px; -} - -.search-bar { - position: relative; -} - -.search-bar .search-input { - border-color: #d9d9d9; - border-radius: 2px; - height: 40px; -} - -.search-bar .fa { - position: absolute; - right: 0; - top: 0; - padding: 12px 12px; - font-size: 16px; - color: grey; -} - -.search-bar .fa:hover { - color: #03a9f4; -} - -/* MAIN --------------------------------------------------- */ -.shadow-box { - margin-bottom: 15px; - border: 1px solid #ebebeb; - border-radius: 0; -} - -.module-thumb { - background: #f5f5f5; - overflow: hidden; - padding: 0; - margin: 0; -} - -.module-thumb img { - display: block; - width: 100%; - transition: all .7s ease; - -webkit-transition: all .7s ease; - -moz-transition: all .7s ease; - -ms-transition: all .7s ease; - -o-transition: all .7s ease; -} - -.module-thumb a:hover img { - transform: scale(1.1); - -webkit-transform: scale(1.1); - -moz-transform: scale(1.1); - -ms-transform: scale(1.1); - -o-transform: scale(1.1); - transition: all .7s ease; - -webkit-transition: all .7s ease; - -moz-transition: all .7s ease; - -ms-transition: all .7s ease; - -o-transition: all .7s ease; -} - -.module-thumb .item-head { - position: absolute; - bottom: 2px; - left: 2px; - right: 2px; - padding: 8px 10px; - font-size: 15px; - color: #fff; - background: -moz-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.3) 30%, rgba(0, 0, 0, 0.7) 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(0, 0, 0, 0)), color-stop(30%, rgba(0, 0, 0, 0.3)), color-stop(100%, rgba(0, 0, 0, 0.7))); - background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.3) 30%, rgba(0, 0, 0, 0.7) 100%); - background: -ms-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.3) 30%, rgba(0, 0, 0, 0.7) 100%); - background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.3) 30%, rgba(0, 0, 0, 0.7) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#99000000', GradientType=0); -} - -.module-thumb .item-head a { - color: #fff; -} - -/* LIST THE IMAGES --------------------------------------------------- */ -.block { - background: #fff; - position: relative; - margin-bottom: 30px; - z-index: 3; -} - -.block a.block-thumbnail { - position: relative; - display: block; - overflow: hidden; - max-height: 200px; - border-top-right-radius: 2px; - border-top-left-radius: 2px; -} - -.block a.block-thumbnail img { - width: 100%; - border-top-right-radius: 2px; - border-top-left-radius: 2px; -} - -.block a.block-thumbnail .thumbnail-overlay { - background-color: rgba(54, 61, 76, .85); - width: 100%; - height: 100%; - display: block; - position: absolute; - top: 0; - left: 0; - opacity: 0; - filter: alpha(opacity=0); - -webkit-transition: all 0.2s ease; - transition: all 0.2s ease; -} - -.block a.block-thumbnail .button-zoom { - position: absolute; - width: 40px; - height: 40px; - line-height: 40px; - border-radius: 2px; - left: 50%; - margin-left: -20px; - bottom: 0; - margin-bottom: -40px; - text-align: center; - -webkit-transition: all 0.2s ease; - transition: all 0.2s ease; -} - -.block a.block-thumbnail:hover .thumbnail-overlay { - opacity: 0.50; - filter: alpha(opacity=50); -} - -.block a.block-thumbnail:hover .button-zoom { - display: block; - bottom: 50%; - margin-bottom: -20px; -} - -.block a.block-thumbnail .details { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - z-index: 2; -} - -.block a.block-thumbnail .details h2 { - float: left; - position: absolute; - -webkit-font-smoothing: antialiased; - box-sizing: border-box; - cursor: auto; - display: inline; - font-size: 12px; - font-weight: bold; - height: auto; - letter-spacing: -0.5px; - line-height: 25px; - text-align: left; - text-decoration: none; - width: auto; - padding: 0 15px; - margin-top: 20px; - margin-bottom: 10px; - bottom: -3px; - max-height: 25px; - overflow: hidden; - color: #ddd; - max-width: 85%; - -} - -.block a.block-thumbnail .details span { - float: right; - color: #fff; - font-size: 10px; - margin-right: 15px; - top: -10px; - position: relative; -} - -.block .block-contents { - width: 100%; - background: #fff; - padding: 0 15px 15px; - border-bottom-left-radius: 2px; - border-bottom-right-radius: 2px; -} - -.block .block-contents a { - color: #333; -} - -.block .block-contents p.tit { - -webkit-font-smoothing: antialiased; - box-sizing: border-box; - color: #333; - display: block; - font-size: 15px; - height: 24px; - padding-top: 1px; - width: 100%; - margin: 15px 0; - position: relative; - overflow: hidden; -} - -.block .block-contents p.tit .label { - float: right; - position: absolute; - right: 0; - top: -1px; - padding: 6px 8px; -} - -.block .block-contents p.tit .label.label-info { - background: #C0C0C0; -} - -.block .block-contents p.desc { - color: #9a9a9a; - clear: both; - font-size: 14px; - max-height: 40px; - min-height: 40px; - overflow: hidden; -} - -/* TOPIC LIST article --------------------------------------------------- */ -.home-topic-list { - /*margin-top: 20px;*/ -} - -.home-topic-list .list-group-item.media .infos { - margin-left: 60px; -} - -.home-topic-list a.meta { - color: #8d8d8d; -} - -.list-panel .panel-body { - padding: 0px 15px; -} - -.topic-list .label { - position: inherit; - font-size: 12px; - font-weight: normal; - padding: 2px 6px; -} - -.topic-list a, .topic-list a:focus, .topic-list a:hover { - color: #555; -} - -.topic-list .list-group-item { - height: 58px; -} - -.topic-list .list-group-item:hover { - background-color: rgba(0, 181, 173, 0.04); -} - -.topic-list .list-group-item .avatar { - margin-top: 2px; -} - -.topic-list .list-group-item .avatar.avatar-middle { - width: 44px; - height: 44px; - padding: 0; - border-color: #eee; -} - -.topic-list .media-heading { - white-space: nowrap; - overflow: hidden; -} - -.topic-list .reply_count_area { - line-height: inherit; -} - -.topic-list .count_of_votes { - font-size: 15px; - margin-top: 11px; - color: #6ebef7; - margin-bottom: -3px; -} - -.topic-list .count_of_replies { - font-size: 15px; - margin-top: 11px; - color: #bab9bb; - margin-bottom: -3px; -} - -.topic-list .count_of_visits { - font-size: 15px; - margin-top: 11px; - color: #bab9bb; - margin-bottom: -3px; -} - -.topic-list .count_set { - font-size: 12px; - color: #bab9bb; -} - -.list-group { - margin-bottom: 0px; -} - -.list-group .list-group-item { - padding: 0px 15px; - border: none; - margin-bottom: 0px; - border-bottom: 1px solid #f2f2f2; -} - -.list-group .list-group-item:first-child { - border-top-left-radius: 0px; - border-top-right-radius: 0px; -} - -.list-group-item.media .infos { - margin-top: 16px; - margin-left: 66px; -} - -.reply_count_area { - width: 200px; - display: inline-block; - text-align: right; - float: left; - line-height: 2em; - margin-top: 18px; -} - -.reply_count_area .count_of_replies { - text-align: center; -} - -.reply_count_area .count_seperator { - margin: 0px 4px; - text-align: center; - font-size: 13px; -} - -.reply_count_area .count_of_visits { - text-align: center; -} - -.reply_last_time { - text-decoration: none; - color: #778087; - font-size: 12px; - display: inline-block; - margin-left: 20px; - margin-top: 18px; - padding-left: 10px; - float: right !important; -} - -.reply_last_time:hover { - text-decoration: none; -} - -.reply_last_time img { - height: 20px; - width: 20px; - vertical-align: middle; - margin-right: .5em; - border-radius: 3px; -} - -.reply_last_time .last_active_time { - text-align: right; - min-width: 50px; - display: inline-block; - white-space: nowrap; - text-decoration: none; -} - -/* LIST article --------------------------------------------------- */ - -.streams { - -} - -.streams .panel-heading { - background-color: #fff; -} - -.streams .topic-filter { - color: #d0d0d0; - font-size: 12px; - margin-top: 8px; - margin-left: 0px; - margin-bottom: 3px; -} - -.streams .topic-filter li { - margin-right: 8px; -} - -.streams .topic-filter a { - color: #8b8a8a; - text-decoration: none; - font-size: 14px; - padding: 1px 4px; - line-height: 18px; -} - -.streams .topic-filter a.active { - color: #03a9f4; -} - -.streams .topic-list .media-heading { - font-size: 15px; - padding-top: 18px; - padding-left: 8px; -} - -.streams .topic-list .media-heading:hover { - cursor: pointer; -} - -.label-default { - background-color: #e5e5e5; - color: #999; -} - -.users-show .list-group .list-group-item { - padding: 10px 15px; -} - -.users-show .notify a { - color: #337ab7; -} - -.users-show .notify a:focus, .users-show .notify a:hover { - color: #03a9f4; -} - -.about-user { - margin-bottom: 20px; -} - -.user-card { - padding: 0; -} - -.user-card .user-avatar { - padding-top: 15px; - padding-bottom: 10px; - text-align: center; -} - -.user-card .user-avatar img { - width: 80px; -} - -.user-card .user-name { - text-align: center; - font-size: 16px; - font-weight: 400; - margin-bottom: 5px; -} - -.about-user .list-group-item { - padding: 10px 15px; -} - -.about-user .user-datas { - padding: 5px 0; -} - -.about-user .user-datas ul { - width: 100%; - height: 50px; - overflow: hidden; - margin: 0; - padding: 0 -} - -.about-user .user-datas ul li { - float: left; - width: 116px; - height: 50px; - text-align: center; - border-right: 1px solid #e8e8e8; -} - -.about-user .user-datas ul li:last-child { - border-right-width: 0; -} - -.about-user .user-datas ul li strong { - height: 26px; - line-height: 26px; - font-size: 26px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-weight: normal; - display: block; - margin-bottom: 5px; - color: rgb(0, 181, 173); -} - -.about-user .user-datas ul li span { - font-size: 12px; - color: #999; -} - -.user-nav { - margin-bottom: 15px; -} - -.user-nav .list-group-item i { - padding-right: 15px; -} - -.user-nav .list-group-item .label { - float: right; -} - -.user-nav:first-child { - margin-top: 15px; -} - -/* Tag --------------------------------------------------- */ -.streams-tags { - padding-top: 30px; -} - -.streams-tags .row-item { - margin-bottom: 15px; -} - -.streams-tags .title { - font-size: 18px; - margin-top: 0; -} - -.streams-tags .title i { - color: #d2d2d2; - margin-right: 5px; -} - -.streams-tags .title .label { - margin-left: 5px; -} - -.streams-tags .media { - background: #f6f6f6; - padding: 10px; - border-radius: 5px; -} - -.streams-tags .media-left a { - padding: 0; -} - -.streams-tags .media-left a img { - width: 100%; - width: 42px; - height: 42px; - border-radius: 25px; -} - -.streams-tags .media-body, .streams-tags .media-left, .streams-tags .media-right { - vertical-align: middle; -} - -.streams-tags .media-heading { - font-size: 14px; - overflow: hidden; - text-overflow: ellipsis; - -webkit-box-orient: vertical; - display: -webkit-box; - -webkit-line-clamp: 1; -} - -.streams-tags .media-heading a { - color: #999; -} - -/* Detail --------------------------------------------------- */ - -.topic .panel-body { - border-bottom: none; -} - -.topic .panel-footer { - background-color: #fff; - border-top: none; -} - -.topic .copyright { - color: #888; - padding: 10px 15px; - background-color: #f4f5f3; - border-radius: 2px; -} - -.topics-show .panel .panel-heading, .blog-pages .panel .panel-heading { - padding-bottom: 12px; - padding: 15px 22px; -} - -.topics-show .topic-title { - font-size: 22px; - color: #333; - text-align: left; - line-height: 135%; - margin-bottom: 12px; - font-weight: normal; -} - -.topics-show .meta .author { - color: #737373; -} - -.topics-show .entry-content { - padding: 22px; -} - -.topics-show .ribbon { - margin-top: 20px; - margin-bottom: -15px; -} - -.topics-show .ribbon .ribbon-excellent { - background: #fffce9; - border-top: 1px solid #f0e0cf; - border-bottom: 1px solid #f0e0cf; - margin: 0px -22px 10px -22px; - padding: 4px 30px; - color: #da974d; - font-size: 14px; -} - -.topics-show .ribbon .ribbon-wiki { - background: #d9edf7; - border-top: 1px solid #DBF5FA; - border-bottom: 1px solid #DBF5FA; - margin: 0px -22px 10px -22px; - padding: 4px 30px; - color: #31708f; - font-size: 14px; -} - -.topics-show .ribbon .ribbon-anchored { - background: #fff2f2; - border-top: 1px solid #ffdba5; - border-bottom: 1px solid #ffdba5; - margin: 0px -22px 10px -22px; - padding: 4px 30px; - color: #ff3d00; - font-size: 14px; -} - -.topics-show .admin-operation { - text-align: center; - margin-top: 10px; - color: #999; -} - -.editor-preview-active-side, .editor-preview, .markdown-body, .markdown-reply { - font-size: 15px; - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100%; - line-height: 1.3; - overflow: hidden; - line-height: 1.6; - word-wrap: break-word; -} - -.editor-preview-active-side > p, .editor-preview > p, .markdown-body > p, .markdown-reply > p { - margin-bottom: 8px; -} - -.markdown-body { - min-height: 202px; - line-height: 26px; - font-size: 16px; - word-wrap: break-word; -} - -.markdown-body p { - margin-bottom: 15px; -} - -.markdown-body a { - color: #4f99cf; -} - -.markdown-body img { - max-width: 100%; - cursor: crosshair; -} - -.markdown-body table { - margin: 10px 0 15px; -} - -.markdown-body table thead { - background-color: #f2f2f2; -} - -.markdown-body table th, -.markdown-body table td { - padding: 10px 20px; - line-height: 22px; - border: 1px solid #DFDFDF; - font-size: 14px; - font-weight: 400; -} - -.vote-box .btn { - padding: 6px 20px; -} - -.voted-users { - padding: 25px 12px 5px; -} - -.voted-users .avatar { - margin: 4px; -} - -.vote-box .btn-primary.active { - background-color: #05c1b8; - border-color: #15cfc7; -} - -.replies-index.replies { - position: relative; -} - -.replies-index.replies .order-links { - position: absolute; - top: 11px; - right: 11px; -} - -.replies-index.replies .order-links .btn-sm { - margin-left: 2px; -} - -.replies-index.replies .order-links .btn-default { - color: #b7b7b7; - background-color: #fff; - border-color: #dcdada; -} - -.replies-index.replies .order-links .btn-default.active { - color: #b7b7b7; - background-color: #fbfbfb; - border-color: #e4e4e4; -} - -.replies-index .operate a { - color: #d0d0d0; - display: inline-block; -} - -.replies-index .markdown-reply { - display: block; - width: 100%; -} - -.replies-index .list-group-item.media { - padding-bottom: 6px; -} - -.replies-index .list-group-item.media { - padding-bottom: 10px; -} - -.replies-index .list-group-item.media .infos, div.replies-index .list-group-item.media .avatar img { - margin-top: 6px; -} - -.replies .list-group-item.media .infos { - margin-left: 90px; -} - -.replies .avatar-container { - text-align: center; - width: 80px; -} - -.replies .label.role { - padding: .1em .2em .1em; - position: inherit; - color: #949090; - border: 1px solid #e0e0e0; - font-weight: normal; - display: inline-block; - margin: 8px 0; - background: transparent; -} - -.replies .media-object.avatar { - display: inline-block; -} - -.reply-post-submit .help-inline { - margin-left: 10px; - font-size: 0.9em; -} - -/* Right --------------------------------------------------- */ -.side-right ul.list { - margin: 0; - padding-left: 2px; -} - -.side-right ul.list a, .side-right ul.list a:focus, .side-right ul.list a:hover { - color: #737373; -} - -.side-right ul.list li { - margin-bottom: 4px; - line-height: 18px; - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 100%; - font-size: 13px; -} - -/* User --------------------------------------------------- */ -.box { - background-color: #fff; - padding: 10px; - margin: 0 0 20px 0; - border: 1px solid #f0f0f0; -} - -.user-basic-info .media-body { - width: auto; -} - -.user-basic-info .role-label { - margin-top: 6px; - text-align: center; -} - -.user-basic-info .role-label .label { - position: inherit; -} - -.user-basic-info .item { - margin: 6px 0; -} - -.user-basic-nav a { - color: inherit; - text-decoration: none; - line-height: 24px; -} - -.user-basic-nav a:hover, .user-basic-nav a.active { - color: #05a1a2; -} - -.user-basic-nav .list-group-item i.fa { - margin-right: 7px; -} - -.user-basic-info .follow-info { - text-align: center; - margin-top: 15px; -} - -.user-basic-info .media-heading { - margin: 10px 0px; -} - -.user-basic-info .follow-info a { - display: block; - text-decoration: none; -} - -.user-basic-info .follow-info a.counter { - font-size: 25px; - color: #00b5ad; -} - -.user-basic-info .follow-info a.counter:hover { - color: #00c4bc; -} - -.user-basic-info .follow-info a.text { - color: #999; -} - -/* 上传头像按钮 */ -.upload-btn { - text-align: center; -} - -.upload-btn label { - background-color: #62c162; - color: white; - padding: 6px 0px; - font-size: 40%; - font-weight: bold; - cursor: pointer; - border-radius: 2px; - position: relative; - overflow: hidden; - display: block; - width: 240px; - margin: 10px auto 0px auto; -} - -.upload-btn label:hover { - background-color: #5cb85c; -} - -.upload-btn span { - cursor: pointer; -} - -.upload-btn input { - position: absolute; - top: 0; - right: 0; - margin: 0; - border: solid transparent; - opacity: .0; - filter: alpha(opacity=0); - direction: ltr; - cursor: pointer; -} - -/* -修改头像 -*/ -.update_ava { - width: 240px; - min-height: 240px; - margin: 10px auto; - text-align: center; - background-color: #fafafa; - border: 1px solid #ebebeb; - border-radius: 3px; -} - -.account-tab > li > a { - border-radius: 2px; - padding: 6px 10px; -} - -.thumbnail-box { - height: 160px; - width: 100%; -} - -.thumbnail-box .convent_choice { - background: url(../images/btn-add-image.png) no-repeat scroll center 25px rgba(0, 0, 0, 0); - border: 1px dashed #E6E6E6; - color: #CCCCCC; - font-size: 18px; - padding-top: 110px; - position: relative; - text-align: center; - top: 0; -} - -/* 上传头像按钮 */ -.thumbnail-box .upload-btn { - text-align: center; - margin-bottom: 9px; -} - -.thumbnail-box .upload-btn label { - background-color: #62c162; - color: white; - padding: 6px 0px; - font-size: 40%; - font-weight: bold; - cursor: pointer; - border-radius: 2px; - position: relative; - overflow: hidden; - display: block; - width: 240px; - margin: 10px auto 0px auto; -} - -.thumbnail-box .upload-btn label:hover { - background-color: #5cb85c; -} - -.thumbnail-box .upload-btn span { - cursor: pointer; -} - -.thumbnail-box .upload-btn input { - position: absolute; - top: 0; - right: 0; - margin: 0; - border: solid transparent; - opacity: .0; - filter: alpha(opacity=0); - direction: ltr; - cursor: pointer; -} - -/* - * Chat - */ -.chats { - background: #fff; -} - -.chats h4 { - display: block; - height: 40px; - line-height: 40px; - font-weight: 500; - text-indent: 10px; - font-size: 14px; -} - -.chats .chat_header { - border-bottom: 1px solid #e5e5e5; -} - -.chats .chat_header h4 { - margin: 5px; - display: block; - height: 40px; - line-height: 40px; - font-weight: 500; -} - -.chats .its { - padding-left: 15px; - padding-right: 15px; -} - -.chats .its li { - display: block; - padding: 15px 0; - border-bottom: 1px solid #e5e5e5; -} - -.chats .its li .avt { - float: left; - display: block; - width: 40px; -} - -.chats .its li .avt img { - width: 100%; - border-radius: 25px; -} - -.chats .chat_body { - margin-left: 50px; - font-size: 13px; - line-height: 18px; -} - -.chats .chat_body h5 { - margin: 0; - display: block; - font-weight: 500; - height: 25px; - line-height: 20px; - overflow: hidden; -} - -.chats .chat_body a { - color: #1f8902; - font-size: 12px; -} - -.chats .chat_body span { - color: #b4b4b4; - margin-left: 5px; -} - -.chats .chat_body .quote { - background: #f4f4f4; - margin-top: 10px; - padding: 10px; -} - -.chats .reply_this a { - color: #ff8a00; - font-size: 12px; - height: 20px; - cursor: pointer; -} - -.chats .chat_p { - color: #9c9c9c; -} - -.chats .chat_reply { -} - -.chat_post { - padding: 15px; -} - -.chat_post .cbox-title { - margin-bottom: 10px; - font-weight: 500; -} - -.chat_post .cbox-post { - border: 1px solid #ccc; -} - -.chat_post .cbox-post a { - color: #9c9c9c -} - -.chat_post .cbox-input { -} - -.chat_post .cbox-input textarea { - border-style: none; - display: block; - width: 100%; - padding: 6px; - font-size: 14px; - line-height: 1.42857143; - color: #555; - overflow-y: auto; - overflow-x: hidden; - resize: none; -} - -.chat_post .fa-2 { - font-size: 1.7em; -} - -.chat_post .cbox-ats { - padding: 0; - display: block; - border-top: 1px solid #ccc; -} - -.chat_post .ats-func { - float: left; -} - -.chat_post .OwO-logo:hover { - color: #ff8a00 !important; -} - -.chat_post .ats-issue { - float: right; -} - -.chat_post .ats-issue .btn { - border-radius: 0 !important; - padding: 5px 16px; -} - -#chat_reply { - color: #1f8902; -} - -.OwO .OwO-logo { - border: none !important; -} - -.channel_featured { - background-color: #5cb85c; - color: #fff; -} - -.channel_top { - background-color: #fb903a; - color: #fff; -} - -/* footer --------------------------------------------------- */ -.footer { - padding: 15px 0; - color: #5f676f; - font-size: 1.3rem; - border-top: 1px solid #e5eff5; - background: #fff; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.footer a { - color: #738a94; - text-decoration: none; -} - -.footer a:hover { - color: #343f44; - text-decoration: none -} - -.footer .footer-row, .footer .footer-nav { - display: -ms-flexbox; - display: flex; - -ms-flex-pack: justify; - justify-content: space-between; -} - -.footer .footer-row { - width: 100%; - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - padding: 10px 0; - border-top: none; -} - -.footer .footer-nav { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; -} - -.footer .footer-nav-logo img { - width: auto; - height: 22px; -} - -.footer .footer-nav-item { - padding: 0 15px; -} - -/* Tablet (Portrait) -================================================== */ -@media only screen and (min-width: 768px) and (max-width: 992px) { - .side-right { - padding: 0 15px 0 15px; - margin-left: 0; - } - - .loop .content-body { - margin-top: 10px; - } -} - -/* Mobile (Portrait & Landscape) -================================================== */ -@media only screen and (max-width: 767px) { - .side-right { - padding: 0 15px 0 15px; - margin-left: 0; - } - - .loop .content-body { - margin-top: 10px; - } - - .loop .content-body p { - display: none; - } -} - -/* Mobile (Landscape) -================================================== */ -@media only screen and (min-width: 480px) and (max-width: 767px) { - .loop .content-body { - margin-top: 10px; - } -} - -/* Mobile (Portrait only) -================================================== */ -@media only screen and (max-width: 479px) { - -} - -#SOHUCS #SOHU_MAIN .section-service-w .service-wrap-b a { - color: #B7B6B6 !important; -} - -/*back to top scroll button*/ -.site-scroll-top { - position: fixed; - z-index: 999; - width: 46px; - height: 46px; - background: url(../images/icon_top.png) no-repeat; - bottom: 45px; - right: 20px; - border-radius: 2px; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; - transition: all .3s; - display: none; -} - -.site-scroll-top:hover { - background-position: 0 -46px; -} \ No newline at end of file diff --git a/src/main/resources/templates/default/dist/images/btn-add-image.png b/src/main/resources/templates/default/dist/images/btn-add-image.png deleted file mode 100644 index bf4544be3b65187c275cf34cd5f8646305c496f2..0000000000000000000000000000000000000000 Binary files a/src/main/resources/templates/default/dist/images/btn-add-image.png and /dev/null differ diff --git a/src/main/resources/templates/default/dist/images/icon_top.png b/src/main/resources/templates/default/dist/images/icon_top.png deleted file mode 100644 index 899638d91a51b10d220aea9d233ce99b752db216..0000000000000000000000000000000000000000 Binary files a/src/main/resources/templates/default/dist/images/icon_top.png and /dev/null differ diff --git a/src/main/resources/templates/default/dist/images/image-overlay-audio-icon.png b/src/main/resources/templates/default/dist/images/image-overlay-audio-icon.png deleted file mode 100644 index 6879a9c526489a2eadc068f585b6fbef52122c7a..0000000000000000000000000000000000000000 Binary files a/src/main/resources/templates/default/dist/images/image-overlay-audio-icon.png and /dev/null differ diff --git a/src/main/resources/templates/default/dist/images/image-overlay-icon.png b/src/main/resources/templates/default/dist/images/image-overlay-icon.png deleted file mode 100644 index 67669db459a264491b64817a09c177034eacd811..0000000000000000000000000000000000000000 Binary files a/src/main/resources/templates/default/dist/images/image-overlay-icon.png and /dev/null differ diff --git a/src/main/resources/templates/default/dist/images/image-overlay-play-icon.png b/src/main/resources/templates/default/dist/images/image-overlay-play-icon.png deleted file mode 100644 index 8c7de1310c66310636b8cf99ca5c0a265ecc1cf5..0000000000000000000000000000000000000000 Binary files a/src/main/resources/templates/default/dist/images/image-overlay-play-icon.png and /dev/null differ diff --git a/src/main/resources/templates/default/dist/images/image-overlay-view-icon.png b/src/main/resources/templates/default/dist/images/image-overlay-view-icon.png deleted file mode 100644 index 3d2d9e9d842aa1d4ec2086fac3797496360c1214..0000000000000000000000000000000000000000 Binary files a/src/main/resources/templates/default/dist/images/image-overlay-view-icon.png and /dev/null differ diff --git a/src/main/resources/templates/default/dist/images/logo.png b/src/main/resources/templates/default/dist/images/logo.png deleted file mode 100644 index ba5201f015d7d9c539a153996a3be35b9aaec996..0000000000000000000000000000000000000000 Binary files a/src/main/resources/templates/default/dist/images/logo.png and /dev/null differ diff --git a/src/main/resources/templates/default/dist/images/preview/1.png b/src/main/resources/templates/default/dist/images/preview/1.png deleted file mode 100644 index 00009826217b2b62003de8dae272159468ede7f5..0000000000000000000000000000000000000000 Binary files a/src/main/resources/templates/default/dist/images/preview/1.png and /dev/null differ diff --git a/src/main/resources/templates/default/dist/images/qq.png b/src/main/resources/templates/default/dist/images/qq.png deleted file mode 100644 index df900043178242ac2440cce001dee8487fddfecd..0000000000000000000000000000000000000000 Binary files a/src/main/resources/templates/default/dist/images/qq.png and /dev/null differ diff --git a/src/main/resources/templates/default/dist/images/weibo.png b/src/main/resources/templates/default/dist/images/weibo.png deleted file mode 100644 index 90a8f0a9716e020888f1f9c8b534cfada3daa8c5..0000000000000000000000000000000000000000 Binary files a/src/main/resources/templates/default/dist/images/weibo.png and /dev/null differ diff --git a/src/main/resources/templates/default/inc/action_message.ftl b/src/main/resources/templates/default/inc/action_message.ftl deleted file mode 100644 index 669a4cec9428b492687b7db9934d7f659a165722..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/inc/action_message.ftl +++ /dev/null @@ -1,19 +0,0 @@ -<#if message??> -
      - <#----> - ${message} -
      - -<#if data??> - <#if (data.code >= 0)> -
      - <#----> - ${data.message} -
      - <#else> -
      - <#----> - ${data.message} -
      - - \ No newline at end of file diff --git a/src/main/resources/templates/default/inc/footer.ftl b/src/main/resources/templates/default/inc/footer.ftl deleted file mode 100644 index d0ba0a0e22a66a8da4ab74e4235954daec9accfb..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/inc/footer.ftl +++ /dev/null @@ -1,25 +0,0 @@ -
      -
      - -
      -
      - - - - diff --git a/src/main/resources/templates/default/inc/header.ftl b/src/main/resources/templates/default/inc/header.ftl deleted file mode 100644 index 2701d576493611b2d8f09da91052b736e0b3604b..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/inc/header.ftl +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - - - - - diff --git a/src/main/resources/templates/default/inc/layout.ftl b/src/main/resources/templates/default/inc/layout.ftl deleted file mode 100644 index c0aa80f88b73aa1c80b75af111ebf15f3fe01949..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/inc/layout.ftl +++ /dev/null @@ -1,95 +0,0 @@ -<#-- Layout --> - - - - - - - - - <@layout.block name="title"> - ${options['site_name']} - - - <@layout.block name="keywords"> - - - - <@layout.block name="keywords"> - - - - - - ${options['site_metas']} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -<@layout.block name="header"> - <#include "/classic/inc/header.ftl"/> - - - - -
      - -
      - <@layout.block name="contents"> -

      Contents will be here

      - -
      -
      - - - -<@layout.block name="footer"> - <#include "/classic/inc/footer.ftl"/> - - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/inc/right.ftl b/src/main/resources/templates/default/inc/right.ftl deleted file mode 100644 index 2b88f16feec27822320c290557e57357055093c9..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/inc/right.ftl +++ /dev/null @@ -1,46 +0,0 @@ -
      -
      -

      热门文章

      -
      -
      - <@sidebar method="hottest_posts"> -
        - <#list results as row> -
      • ${row_index + 1}. ${row.title}
      • - -
      - -
      -
      - -
      -
      -

      最新发布

      -
      -
      - <@sidebar method="latest_posts"> -
        - <#list results as row> -
      • ${row_index + 1}. ${row.title}
      • - -
      - -
      -
      - -<@controls name="comment"> -
      -
      -

      最新评论

      -
      -
      - <@sidebar method="latest_comments"> - - -
      -
      - \ No newline at end of file diff --git a/src/main/resources/templates/default/index.ftl b/src/main/resources/templates/default/index.ftl deleted file mode 100644 index 2187589bbb2fa34e8d7306c1a35c3acb995b94db..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/index.ftl +++ /dev/null @@ -1,67 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - - <@layout.put block="contents"> - <#assign topId = 1 /> - -
      - <@contents channelId=topId size=8> - <#list results.content as row> - - - -
      - - - -
      -
      -

      最近更新

      -
      - -
      - <@contents size=30> - - -
      -
      - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/settings/avatar.ftl b/src/main/resources/templates/default/settings/avatar.ftl deleted file mode 100644 index 106fdad574ed1d5560634ef022e561e276fb2075..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/settings/avatar.ftl +++ /dev/null @@ -1,36 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 修改用户信息 - - - <@layout.put block="contents"> -
      - -
      -
      - <#include "/default/inc/action_message.ftl"/> -
      -
      - -
      -
      - [Example] -
      -
      -
      - - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/settings/email.ftl b/src/main/resources/templates/default/settings/email.ftl deleted file mode 100644 index eb1d64709ab5e36c90f5529f10d972eceb64efe1..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/settings/email.ftl +++ /dev/null @@ -1,55 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 修改邮箱 - - - <@layout.put block="contents"> -
      - -
      -
      - <#include "/default/inc/action_message.ftl"/> -
      -
      -
      -
      - -
      -
      - - - - -
      -
      -
      -
      - -
      - -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      - - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/settings/password.ftl b/src/main/resources/templates/default/settings/password.ftl deleted file mode 100644 index 1fec3ad36c4252184ea966dd6b5b1fe8a3ea82c5..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/settings/password.ftl +++ /dev/null @@ -1,56 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 修改用户信息 - - - <@layout.put block="contents"> -
      - -
      -
      - <#include "/default/inc/action_message.ftl"/> -
      -
      -
      -
      - -
      - -
      -
      -
      - -
      - -
      -
      -
      - -
      - -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      - - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/settings/profile.ftl b/src/main/resources/templates/default/settings/profile.ftl deleted file mode 100644 index 04914ba8070404f886d356a15fdac9affb496640..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/settings/profile.ftl +++ /dev/null @@ -1,50 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 修改用户信息 - - - <@layout.put block="contents"> -
      - -
      -
      - <#include "/default/inc/action_message.ftl"/> -
      -
      -
      -
      - -
      - -
      -
      -
      - -
      - -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      - - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/tag/index.ftl b/src/main/resources/templates/default/tag/index.ftl deleted file mode 100644 index 94b7433d139609cd73b8da9dd66f2ebfcc692ef4..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/tag/index.ftl +++ /dev/null @@ -1,51 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 标签列表 - - - <@layout.put block="contents"> -
      -
      -
      -
      - <#list results.content as row> - <#assign post = row.post /> -
      -

      - ${row.name} - ${row.posts} -

      - <#if post??> -
      -
      - <@utils.showAva post.author "media-object"/> -
      - -
      - -
      - - <#if results.content?size == 0> -
    • -
      -
      该目录下还没有内容!
      -
      -
    • - -
      -
      - -
      - <@utils.pager request.requestURI!"", results, 5/> -
      -
      -
      - <#include "/classic/inc/right.ftl" /> -
      -
      - - diff --git a/src/main/resources/templates/default/tag/view.ftl b/src/main/resources/templates/default/tag/view.ftl deleted file mode 100644 index 2021c5f54f404553e15efb6d0128a381016a4d8f..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/tag/view.ftl +++ /dev/null @@ -1,68 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 标签: ${kw} - - - <@layout.put block="contents"> -
      -
      -
      -
      -
        -
      • - 标签: ${name} 共 ${results.totalElements} 个结果. -
      • -
      -
      -
      - -
      - - -
      - - -
      -
      -
      - <#include "/default/inc/right.ftl" /> -
      -
      - - \ No newline at end of file diff --git a/src/main/resources/templates/default/user/method_comments.ftl b/src/main/resources/templates/default/user/method_comments.ftl deleted file mode 100644 index b3c39deabbb1a17e389703aa68bb832b668c04f0..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/user/method_comments.ftl +++ /dev/null @@ -1,84 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - ${user.name}的评论 - - - <@layout.put block="contents"> -
      -
      - <#include "/default/inc/user_sidebar.ftl"/> -
      -
      -
      -
      发表的评论
      - <@user_comments userId=user.id pageNo=pageNo> -
      -
        - <#list results.content as row> -
      • - <#if row.post??> - ${row.post.title} - <#else> - 文章已删除 - - - ${timeAgo(row.created)} - - - - -
        -

        ${row.content}

        -
        -
      • - - - <#if results.content?size == 0> -
      • -
        -
        该目录下还没有内容!
        -
        -
      • - -
      -
      - - -
      -
      -
      - - - - - diff --git a/src/main/resources/templates/default/user/method_favorites.ftl b/src/main/resources/templates/default/user/method_favorites.ftl deleted file mode 100644 index 345b26367b022e27a08da57774c9e8d2a4437b76..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/user/method_favorites.ftl +++ /dev/null @@ -1,81 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - ${user.name}的收藏 - - - <@layout.put block="contents"> -
      -
      - <#include "/default/inc/user_sidebar.ftl"/> -
      -
      -
      -
      收藏的文章
      - <@user_favorites userId=user.id pageNo=pageNo> -
      -
        - <#list results.content as row> - <#assign target = row.post /> -
      • - <#if target??> - ${target.title} - <#else> - 文章已删除 - - - ${timeAgo(row.created)} - - - -
      • - - - <#if results.content?size == 0> -
      • -
        -
        该目录下还没有内容!
        -
        -
      • - -
      -
      - - -
      -
      -
      - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/user/method_messages.ftl b/src/main/resources/templates/default/user/method_messages.ftl deleted file mode 100644 index e408f69956bd8e9430d58f7b665e75ba166798dd..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/user/method_messages.ftl +++ /dev/null @@ -1,63 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 通知 - - - <@layout.put block="contents"> -
      -
      - <#include "/default/inc/user_sidebar.ftl"/> -
      -
      -
      -
      通知列表
      - <@user_messages userId=user.id pageNo=pageNo> -
      -
        - <#list results.content as row> -
      • - -
        - <@utils.showAva row.from "media-object img-thumbnail avatar avatar-middle"/> -
        -
        -
        - <#----> - ${row.from.name} - - <#if (row.event == 1)> - 收藏了你的文章 - ${row.post.title} - <#elseif (row.event == 3)> - 评论了你的文章 - 点击查看详情 - <#elseif (row.event == 4)> - 回复了你的评论 - 点击查看详情 - - -
        -
        -
      • - - - <#if results.content?size == 0> -
      • -
        -
        该目录下还没有内容!
        -
        -
      • - -
      -
      - - -
      -
      -
      - - - \ No newline at end of file diff --git a/src/main/resources/templates/default/user/method_posts.ftl b/src/main/resources/templates/default/user/method_posts.ftl deleted file mode 100644 index bc7a4fde0da06e16b281a6f96a86f846dd15358e..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/default/user/method_posts.ftl +++ /dev/null @@ -1,89 +0,0 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - ${user.name}的文章 - - - <@layout.put block="contents"> -
      -
      - <#include "/default/inc/user_sidebar.ftl"/> -
      -
      -
      -
      发表的文章
      - <@user_contents userId=user.id pageNo=pageNo> -
      -
        - <#list results.content as row> -
      • - ${row.title} - - ${row.favors} 点赞 - - ${row.comments} 回复 - - ${timeAgo(row.created)} - - - -
      • - - - <#if results.content?size == 0> -
      • -
        -
        该目录下还没有内容!
        -
        -
      • - -
      -
      - - -
      -
      -
      - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/graduation/README.md b/src/main/resources/templates/graduation/README.md new file mode 100644 index 0000000000000000000000000000000000000000..32897f42aefed3b0d9770befec64cdbfa7666d73 --- /dev/null +++ b/src/main/resources/templates/graduation/README.md @@ -0,0 +1,68 @@ +### 🔥Youth开源主题 (支持响应式) + +### 程序: +* Mblog 开源Java博客系统 + +* gitee:https://gitee.com/mtons/mblog + +* github:https://github.com/langhsu/mblog + +### 配置: + ```xml +配置文件:src/main/resources/application.yml + +上传/轮播:thumbnail_post_size: 1920x1080(最佳) banner + +文章/配图:storage_max_width: 800 (最佳) markdown0 + + ``` +### 安装教程 + +开发模式:将模板直接放mblog-master\src\main\resources\templates下 模板命名为 youth +上传模式:上传模板文件为 youth.zip 格式 + +### 问题解答 + +* 1.在master中(克隆/下载)的模板,压缩包文件名是不对的,改成youth.zip上传就没问题了 +* 2.为了方便模板下载,在下面直接有下载链接,可直接上传 +* 3.开发模式下,模板没加载上,问题:↓ +* 4.不要把zip直接放到templates下,应把文件夹放到templates下,将下载的压缩包zip解压出来,文件名为:youth,然后进入后台即可更换 +* 5.如图 +![如图](https://images.gitee.com/uploads/images/2019/0626/154917_531b7533_1758849.png "templates") + +* 6 轮播图,文章,都可以控制显示条数,页面中,有体现到,如轮播图,初始化显示3条 (size=3) 可修改任意条数! + +* 7.轮播图不显示,主程序初始化表里面,自带了轮播图那张表'mto_channel',参数Key是banner 如果删除了banner,自己在创建,最好把数据库的字段id,改成 1 除了轮播图,对应的文章表则是:channel_id 和轮播图id统一即可,修改一样即可显示。其他栏目可自行修改或是删除!banner隐藏了,文章也隐藏,但是轮播图不会隐藏,轮播图主要控制轮播图的文章,其他的文章可以创建其他栏目,不要混在一起,轮播图的名称也可以修改,比如banner名称,改成任意中文名,Key则为英文,轮播图和栏目文章不要搞混在一起。> 修改后重启程序 + + 【就比如,轮播图,重要的文章3个,就推到banner上,将轮播图隐藏,不在前端栏目显示,但是轮播内容,还是有的,剩下其他的文章,都分类到其他栏目,如果你想发多条,可以把size=3 参数,改成任意数。】 + +* 8.后台栏目缩图,是给栏目添加的小图标 在Delicate主题上,有呈现出来,如果想在其他模板上加,可直接获取channels表中的thumbnail字段 + + +有请提示:程序和模板,都是开源的,有的人觉得有些地方用的不方便,作者本来就辛苦开源出来,东西没有完美的,要一点一点完善,希望谅解一下! + +### 模板下载区 + +### Youth主题v1.0 +[下载主题v1.0](https://gitee.com/cuiweiboy/youth/attach_files/233709/download) + +[演示图片](https://files.gitee.com/group1/M00/08/43/PaAvDF0TGnCAefuXAAw94LRP50I33.jpeg?token=692dad38548adef9576320bdd1e8d34c&ts=1561533080&attname=Youth%E4%B8%BB%E9%A2%98v1.0.jpeg&disposition=inline "演示图片预览") + +### Youth主题v1.1 +[下载模板v1.1](https://gitee.com/cuiweiboy/youth/attach_files/241803/download) + +[演示图片](https://files.gitee.com/group1/M00/08/43/PaAvDF0TGnmACJhcAAqa94Ql7b8972.png?token=398b4fc433ad425d811f74f1da3725ed&ts=1561533080&attname=Youth%E4%B8%BB%E9%A2%98v1.1.png&disposition=inline "演示图片预览") + +### Youth主题v2.1 +[下载模板v2.1](https://gitee.com/cuiweiboy/youth/attach_files/235656/download) + +[演示图片](https://files.gitee.com/group1/M00/08/43/PaAvDF0TGoiAITEcAAyxQ3kb9-o53.jpeg?token=cd78527e01f9ada132788e9b7a63db87&ts=1561533080&attname=Youth%E4%B8%BB%E9%A2%98v2.0.jpeg&disposition=inline "演示图片预览") + +### Youth主题v2.2 +[下载主题v2.2](https://gitee.com/cuiweiboy/youth/attach_files/240602/download) + +[演示图片](https://files.gitee.com/group1/M00/08/43/PaAvDF0TGpiAV1OSAAwIQhjL1bY573.png?token=4391c1ed2ce27b10000fa7314f6efd31&ts=1561533080&attname=Youth%E4%B8%BB%E9%A2%98v2.1.png&disposition=inline "演示图片预览") + +### Youth主题v3 (最新) +[下载Youth主题v3](https://gitee.com/cuiweiboy/youth/attach_files/249852/download) +![Youth主题v3](https://images.gitee.com/uploads/images/2019/0612/153340_0259c28b_1758849.jpeg "2019-06-12_153247.jpg") diff --git a/src/main/resources/templates/graduation/about.json b/src/main/resources/templates/graduation/about.json new file mode 100644 index 0000000000000000000000000000000000000000..5c5fe4846d185c958d7e87d76577928763995d88 --- /dev/null +++ b/src/main/resources/templates/graduation/about.json @@ -0,0 +1,10 @@ +{ + "name": "graduation", + "slogan": "毕设主题", + "version": "V1", + "author": "Logan", + "website": "www.loganforbid.top", + "previews": [ + "/theme/graduation/dist/images/preview/1.png" + ] +} \ No newline at end of file diff --git a/src/main/resources/templates/graduation/auth/forgot.ftl b/src/main/resources/templates/graduation/auth/forgot.ftl new file mode 100644 index 0000000000000000000000000000000000000000..b2babf68825a7d77fcc3bacd480d76acc07e6c94 --- /dev/null +++ b/src/main/resources/templates/graduation/auth/forgot.ftl @@ -0,0 +1,65 @@ +<#include "/graduation/inc/layout.ftl"/> + +<@layout "重置密码"> + +
      +
      +
      +
      +

      找回密码

      +
      +
      +
      + <#include "/graduation/inc/action_message.ftl"/> +
      +
      +
      + +
      + + + + +
      +
      +
      + + +
      +
      + + +
      +
      + + +
      + +
      +
      +
      + +
      +
      + +
      +
      + +
      +
      +
      + + +
      +
      +
      +
      +
      + + \ No newline at end of file diff --git a/src/main/resources/templates/graduation/auth/login.ftl b/src/main/resources/templates/graduation/auth/login.ftl new file mode 100644 index 0000000000000000000000000000000000000000..a6ecfcc5ea6e280925ab7cde63518c7c07330d34 --- /dev/null +++ b/src/main/resources/templates/graduation/auth/login.ftl @@ -0,0 +1,65 @@ +<#include "/graduation/inc/layout.ftl"/> +<@layout> + +
      +
      +
      +
      +

      请登录

      +
      +
      +
      <#include "/graduation/inc/action_message.ftl"/>
      +
      +
      + + +
      +
      + + +
      +
      +
      + + + + 忘记密码? + + +
      + +
      +
      + +
      + <@controls name="register"> +
      + <#if site.hasValue("weibo_client_id")> + + 微博登录 + + + <#if site.hasValue("qq_app_id")> + + QQ登录 + + + <#if site.hasValue("github_client_id")> + + Github登录 + + +
      + +
      +
      +
      +
      +
      + + + diff --git a/src/main/resources/templates/graduation/auth/oauth_register.ftl b/src/main/resources/templates/graduation/auth/oauth_register.ftl new file mode 100644 index 0000000000000000000000000000000000000000..cf2f67bab2cbd2637b0505a3309bc410bf8cdacb --- /dev/null +++ b/src/main/resources/templates/graduation/auth/oauth_register.ftl @@ -0,0 +1,41 @@ +<#include "/graduation/inc/layout.ftl"/> + +<@layout "注册"> + +
      +
      +
      +
      +

      离成功只差一步

      +
      +
      +
      <#include "/graduation/inc/action_message.ftl"/>
      +
      + + + + + + + + + +
      + + +
      + +
      +
      +
      +
      +
      + + + \ No newline at end of file diff --git a/src/main/resources/templates/graduation/auth/register.ftl b/src/main/resources/templates/graduation/auth/register.ftl new file mode 100644 index 0000000000000000000000000000000000000000..9ca1c4c7bc945fc9cc32ce404f877d61f1e7812d --- /dev/null +++ b/src/main/resources/templates/graduation/auth/register.ftl @@ -0,0 +1,57 @@ +<#include "/graduation/inc/layout.ftl"/> + +<@layout "注册"> +
      +
      +
      +
      +

      注册

      +
      +
      + <#include "/graduation/inc/action_message.ftl"/> +
      +
      +
      +
      + + +
      + <@controls name="register_email_validate"> +
      + +
      + + + + +
      +
      +
      + + +
      + +
      + + +
      +
      + + +
      + +
      +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/src/main/resources/templates/graduation/channel/editing.ftl b/src/main/resources/templates/graduation/channel/editing.ftl new file mode 100644 index 0000000000000000000000000000000000000000..678324c2b3511f015c78be0a3878d9b9602c4c7d --- /dev/null +++ b/src/main/resources/templates/graduation/channel/editing.ftl @@ -0,0 +1,73 @@ +<#include "/graduation/inc/layout.ftl"/> +<@layout "编辑文章"> + +
      + + +
      +
      +
      + <#if view??> + + + + + +
      + +
      +
      + <#include "/graduation/channel/editor/${editor}.ftl"/> +
      +
      +
      +
      +
      +
      style="background: url(<@resource src=view.thumbnail/>);" > +
      + +
      +
      +
      +
      +
      +
      +

      发布到

      +
      +
      + +
      +
      +
      +
      +

      标签(用逗号或空格分隔)

      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + + diff --git a/src/main/resources/templates/classic/channel/editor/markdown.ftl b/src/main/resources/templates/graduation/channel/editor/markdown.ftl similarity index 100% rename from src/main/resources/templates/classic/channel/editor/markdown.ftl rename to src/main/resources/templates/graduation/channel/editor/markdown.ftl diff --git a/src/main/resources/templates/classic/channel/editor/tinymce.ftl b/src/main/resources/templates/graduation/channel/editor/tinymce.ftl similarity index 100% rename from src/main/resources/templates/classic/channel/editor/tinymce.ftl rename to src/main/resources/templates/graduation/channel/editor/tinymce.ftl diff --git a/src/main/resources/templates/default/search.ftl b/src/main/resources/templates/graduation/channel/index.ftl similarity index 32% rename from src/main/resources/templates/default/search.ftl rename to src/main/resources/templates/graduation/channel/index.ftl index 85b95cb60a96434f3bdda5a54b2174f164991119..aa7fc6daa8a039f93bfe568abc34213d20b3c57b 100644 --- a/src/main/resources/templates/default/search.ftl +++ b/src/main/resources/templates/graduation/channel/index.ftl @@ -1,20 +1,26 @@ -<@layout.extends name="/inc/layout.ftl"> - <@layout.put block="title"> - 搜索: ${kw} - +<#include "/graduation/inc/layout.ftl"/> +<@layout channel.name> +
      +
      +
      +
      + +
      +
      + <@contents channelId=channel.id pageNo=pageNo order=order> - <#if !results?? || results.content?size == 0> + <#if results.content?size == 0>
    • 该目录下还没有内容!
      @@ -55,14 +60,77 @@
      -
    • + +
      + +
      + + + '),s.append(r)),ee.extend(t,{$el:s,el:s[0],$dragEl:r,dragEl:r[0]}),i.draggable&&t.enableDraggable()}},destroy:function(){this.scrollbar.disableDraggable()}},B={setTransform:function(e,t){var a=this.rtl,i=L(e),s=a?-1:1,r=i.attr("data-swiper-parallax")||"0",n=i.attr("data-swiper-parallax-x"),o=i.attr("data-swiper-parallax-y"),l=i.attr("data-swiper-parallax-scale"),d=i.attr("data-swiper-parallax-opacity");if(n||o?(n=n||"0",o=o||"0"):this.isHorizontal()?(n=r,o="0"):(o=r,n="0"),n=0<=n.indexOf("%")?parseInt(n,10)*t*s+"%":n*t*s+"px",o=0<=o.indexOf("%")?parseInt(o,10)*t+"%":o*t+"px",null!=d){var p=d-(d-1)*(1-Math.abs(t));i[0].style.opacity=p}if(null==l)i.transform("translate3d("+n+", "+o+", 0px)");else{var c=l-(l-1)*(1-Math.abs(t));i.transform("translate3d("+n+", "+o+", 0px) scale("+c+")")}},setTranslate:function(){var i=this,e=i.$el,t=i.slides,s=i.progress,r=i.snapGrid;e.children("[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]").each(function(e,t){i.parallax.setTransform(t,s)}),t.each(function(e,t){var a=t.progress;1i.maxRatio&&(a.scale=i.maxRatio-1+Math.pow(a.scale-i.maxRatio+1,.5)),a.scales.touchesStart.x))return void(s.isTouched=!1);if(!t.isHorizontal()&&(Math.floor(s.minY)===Math.floor(s.startY)&&s.touchesCurrent.ys.touchesStart.y))return void(s.isTouched=!1)}e.preventDefault(),e.stopPropagation(),s.isMoved=!0,s.currentX=s.touchesCurrent.x-s.touchesStart.x+s.startX,s.currentY=s.touchesCurrent.y-s.touchesStart.y+s.startY,s.currentXs.maxX&&(s.currentX=s.maxX-1+Math.pow(s.currentX-s.maxX+1,.8)),s.currentYs.maxY&&(s.currentY=s.maxY-1+Math.pow(s.currentY-s.maxY+1,.8)),r.prevPositionX||(r.prevPositionX=s.touchesCurrent.x),r.prevPositionY||(r.prevPositionY=s.touchesCurrent.y),r.prevTime||(r.prevTime=Date.now()),r.x=(s.touchesCurrent.x-r.prevPositionX)/(Date.now()-r.prevTime)/2,r.y=(s.touchesCurrent.y-r.prevPositionY)/(Date.now()-r.prevTime)/2,Math.abs(s.touchesCurrent.x-r.prevPositionX)<2&&(r.x=0),Math.abs(s.touchesCurrent.y-r.prevPositionY)<2&&(r.y=0),r.prevPositionX=s.touchesCurrent.x,r.prevPositionY=s.touchesCurrent.y,r.prevTime=Date.now(),i.$imageWrapEl.transform("translate3d("+s.currentX+"px, "+s.currentY+"px,0)")}}},onTouchEnd:function(){var e=this.zoom,t=e.gesture,a=e.image,i=e.velocity;if(t.$imageEl&&0!==t.$imageEl.length){if(!a.isTouched||!a.isMoved)return a.isTouched=!1,void(a.isMoved=!1);a.isTouched=!1,a.isMoved=!1;var s=300,r=300,n=i.x*s,o=a.currentX+n,l=i.y*r,d=a.currentY+l;0!==i.x&&(s=Math.abs((o-a.currentX)/i.x)),0!==i.y&&(r=Math.abs((d-a.currentY)/i.y));var p=Math.max(s,r);a.currentX=o,a.currentY=d;var c=a.width*e.scale,u=a.height*e.scale;a.minX=Math.min(t.slideWidth/2-c/2,0),a.maxX=-a.minX,a.minY=Math.min(t.slideHeight/2-u/2,0),a.maxY=-a.minY,a.currentX=Math.max(Math.min(a.currentX,a.maxX),a.minX),a.currentY=Math.max(Math.min(a.currentY,a.maxY),a.minY),t.$imageWrapEl.transition(p).transform("translate3d("+a.currentX+"px, "+a.currentY+"px,0)")}},onTransitionEnd:function(){var e=this.zoom,t=e.gesture;t.$slideEl&&this.previousIndex!==this.activeIndex&&(t.$imageEl.transform("translate3d(0,0,0) scale(1)"),t.$imageWrapEl.transform("translate3d(0,0,0)"),e.scale=1,e.currentScale=1,t.$slideEl=void 0,t.$imageEl=void 0,t.$imageWrapEl=void 0)},toggle:function(e){var t=this.zoom;t.scale&&1!==t.scale?t.out():t.in(e)},in:function(e){var t,a,i,s,r,n,o,l,d,p,c,u,h,v,f,m,g=this,b=g.zoom,w=g.params.zoom,y=b.gesture,x=b.image;(y.$slideEl||(y.$slideEl=g.clickedSlide?L(g.clickedSlide):g.slides.eq(g.activeIndex),y.$imageEl=y.$slideEl.find("img, svg, canvas"),y.$imageWrapEl=y.$imageEl.parent("."+w.containerClass)),y.$imageEl&&0!==y.$imageEl.length)&&(y.$slideEl.addClass(""+w.zoomedSlideClass),void 0===x.touchesStart.x&&e?(t="touchend"===e.type?e.changedTouches[0].pageX:e.pageX,a="touchend"===e.type?e.changedTouches[0].pageY:e.pageY):(t=x.touchesStart.x,a=x.touchesStart.y),b.scale=y.$imageWrapEl.attr("data-swiper-zoom")||w.maxRatio,b.currentScale=y.$imageWrapEl.attr("data-swiper-zoom")||w.maxRatio,e?(f=y.$slideEl[0].offsetWidth,m=y.$slideEl[0].offsetHeight,i=y.$slideEl.offset().left+f/2-t,s=y.$slideEl.offset().top+m/2-a,o=y.$imageEl[0].offsetWidth,l=y.$imageEl[0].offsetHeight,d=o*b.scale,p=l*b.scale,h=-(c=Math.min(f/2-d/2,0)),v=-(u=Math.min(m/2-p/2,0)),(r=i*b.scale)>1]<=t?i=s:a=s;return a};return this.x=e,this.y=t,this.lastIndex=e.length-1,this.interpolate=function(e){return e?(n=o(this.x,e),r=n-1,(e-this.x[r])*(this.y[n]-this.y[r])/(this.x[n]-this.x[r])+this.y[r]):0},this},getInterpolateFunction:function(e){var t=this;t.controller.spline||(t.controller.spline=t.params.loop?new V.LinearSpline(t.slidesGrid,e.slidesGrid):new V.LinearSpline(t.snapGrid,e.snapGrid))},setTranslate:function(e,t){var a,i,s=this,r=s.controller.control;function n(e){var t=s.rtlTranslate?-s.translate:s.translate;"slide"===s.params.controller.by&&(s.controller.getInterpolateFunction(e),i=-s.controller.spline.interpolate(-t)),i&&"container"!==s.params.controller.by||(a=(e.maxTranslate()-e.minTranslate())/(s.maxTranslate()-s.minTranslate()),i=(t-s.minTranslate())*a+e.minTranslate()),s.params.controller.inverse&&(i=e.maxTranslate()-i),e.updateProgress(i),e.setTranslate(i,s),e.updateActiveIndex(),e.updateSlidesClasses()}if(Array.isArray(r))for(var o=0;o'),i.append(e)),e.css({height:r+"px"})):0===(e=a.find(".swiper-cube-shadow")).length&&(e=L('
      '),a.append(e)));for(var h=0;h'),v.append(E)),0===S.length&&(S=L('
      '),v.append(S)),E.length&&(E[0].style.opacity=Math.max(-b,0)),S.length&&(S[0].style.opacity=Math.max(b,0))}}if(i.css({"-webkit-transform-origin":"50% 50% -"+l/2+"px","-moz-transform-origin":"50% 50% -"+l/2+"px","-ms-transform-origin":"50% 50% -"+l/2+"px","transform-origin":"50% 50% -"+l/2+"px"}),d.shadow)if(p)e.transform("translate3d(0px, "+(r/2+d.shadowOffset)+"px, "+-r/2+"px) rotateX(90deg) rotateZ(0deg) scale("+d.shadowScale+")");else{var C=Math.abs(u)-90*Math.floor(Math.abs(u)/90),M=1.5-(Math.sin(2*C*Math.PI/360)/2+Math.cos(2*C*Math.PI/360)/2),z=d.shadowScale,P=d.shadowScale/M,k=d.shadowOffset;e.transform("scale3d("+z+", 1, "+P+") translate3d(0px, "+(n/2+k)+"px, "+-n/2/P+"px) rotateX(-90deg)")}var $=I.isSafari||I.isUiWebView?-l/2:0;i.transform("translate3d(0px,0,"+$+"px) rotateX("+(t.isHorizontal()?0:u)+"deg) rotateY("+(t.isHorizontal()?-u:0)+"deg)")},setTransition:function(e){var t=this.$el;this.slides.transition(e).find(".swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left").transition(e),this.params.cubeEffect.shadow&&!this.isHorizontal()&&t.find(".swiper-cube-shadow").transition(e)}},K={setTranslate:function(){for(var e=this,t=e.slides,a=e.rtlTranslate,i=0;i'),s.append(p)),0===c.length&&(c=L('
      '),s.append(c)),p.length&&(p[0].style.opacity=Math.max(-r,0)),c.length&&(c[0].style.opacity=Math.max(r,0))}s.transform("translate3d("+l+"px, "+d+"px, 0px) rotateX("+o+"deg) rotateY("+n+"deg)")}},setTransition:function(e){var a=this,t=a.slides,i=a.activeIndex,s=a.$wrapperEl;if(t.transition(e).find(".swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left").transition(e),a.params.virtualTranslate&&0!==e){var r=!1;t.eq(i).transitionEnd(function(){if(!r&&a&&!a.destroyed){r=!0,a.animating=!1;for(var e=["webkitTransitionEnd","transitionend"],t=0;t'),v.append(E)),0===S.length&&(S=L('
      '),v.append(S)),E.length&&(E[0].style.opacity=0')}}),Object.keys(F).forEach(function(e){t.a11y[e]=F[e].bind(t)})},on:{init:function(){this.params.a11y.enabled&&(this.a11y.init(),this.a11y.updateNavigation())},toEdge:function(){this.params.a11y.enabled&&this.a11y.updateNavigation()},fromEdge:function(){this.params.a11y.enabled&&this.a11y.updateNavigation()},paginationUpdate:function(){this.params.a11y.enabled&&this.a11y.updatePagination()},destroy:function(){this.params.a11y.enabled&&this.a11y.destroy()}}},{name:"history",params:{history:{enabled:!1,replaceState:!1,key:"slides"}},create:function(){var e=this;ee.extend(e,{history:{init:R.init.bind(e),setHistory:R.setHistory.bind(e),setHistoryPopState:R.setHistoryPopState.bind(e),scrollToSlide:R.scrollToSlide.bind(e),destroy:R.destroy.bind(e)}})},on:{init:function(){this.params.history.enabled&&this.history.init()},destroy:function(){this.params.history.enabled&&this.history.destroy()},transitionEnd:function(){this.history.initialized&&this.history.setHistory(this.params.history.key,this.activeIndex)}}},{name:"hash-navigation",params:{hashNavigation:{enabled:!1,replaceState:!1,watchState:!1}},create:function(){var e=this;ee.extend(e,{hashNavigation:{initialized:!1,init:q.init.bind(e),destroy:q.destroy.bind(e),setHash:q.setHash.bind(e),onHashCange:q.onHashCange.bind(e)}})},on:{init:function(){this.params.hashNavigation.enabled&&this.hashNavigation.init()},destroy:function(){this.params.hashNavigation.enabled&&this.hashNavigation.destroy()},transitionEnd:function(){this.hashNavigation.initialized&&this.hashNavigation.setHash()}}},{name:"autoplay",params:{autoplay:{enabled:!1,delay:3e3,waitForTransition:!0,disableOnInteraction:!0,stopOnLastSlide:!1,reverseDirection:!1}},create:function(){var t=this;ee.extend(t,{autoplay:{running:!1,paused:!1,run:W.run.bind(t),start:W.start.bind(t),stop:W.stop.bind(t),pause:W.pause.bind(t),onTransitionEnd:function(e){t&&!t.destroyed&&t.$wrapperEl&&e.target===this&&(t.$wrapperEl[0].removeEventListener("transitionend",t.autoplay.onTransitionEnd),t.$wrapperEl[0].removeEventListener("webkitTransitionEnd",t.autoplay.onTransitionEnd),t.autoplay.paused=!1,t.autoplay.running?t.autoplay.run():t.autoplay.stop())}}})},on:{init:function(){this.params.autoplay.enabled&&this.autoplay.start()},beforeTransitionStart:function(e,t){this.autoplay.running&&(t||!this.params.autoplay.disableOnInteraction?this.autoplay.pause(e):this.autoplay.stop())},sliderFirstMove:function(){this.autoplay.running&&(this.params.autoplay.disableOnInteraction?this.autoplay.stop():this.autoplay.pause())},destroy:function(){this.autoplay.running&&this.autoplay.stop()}}},{name:"effect-fade",params:{fadeEffect:{crossFade:!1}},create:function(){ee.extend(this,{fadeEffect:{setTranslate:j.setTranslate.bind(this),setTransition:j.setTransition.bind(this)}})},on:{beforeInit:function(){var e=this;if("fade"===e.params.effect){e.classNames.push(e.params.containerModifierClass+"fade");var t={slidesPerView:1,slidesPerColumn:1,slidesPerGroup:1,watchSlidesProgress:!0,spaceBetween:0,virtualTranslate:!0};ee.extend(e.params,t),ee.extend(e.originalParams,t)}},setTranslate:function(){"fade"===this.params.effect&&this.fadeEffect.setTranslate()},setTransition:function(e){"fade"===this.params.effect&&this.fadeEffect.setTransition(e)}}},{name:"effect-cube",params:{cubeEffect:{slideShadows:!0,shadow:!0,shadowOffset:20,shadowScale:.94}},create:function(){ee.extend(this,{cubeEffect:{setTranslate:U.setTranslate.bind(this),setTransition:U.setTransition.bind(this)}})},on:{beforeInit:function(){var e=this;if("cube"===e.params.effect){e.classNames.push(e.params.containerModifierClass+"cube"),e.classNames.push(e.params.containerModifierClass+"3d");var t={slidesPerView:1,slidesPerColumn:1,slidesPerGroup:1,watchSlidesProgress:!0,resistanceRatio:0,spaceBetween:0,centeredSlides:!1,virtualTranslate:!0};ee.extend(e.params,t),ee.extend(e.originalParams,t)}},setTranslate:function(){"cube"===this.params.effect&&this.cubeEffect.setTranslate()},setTransition:function(e){"cube"===this.params.effect&&this.cubeEffect.setTransition(e)}}},{name:"effect-flip",params:{flipEffect:{slideShadows:!0,limitRotation:!0}},create:function(){ee.extend(this,{flipEffect:{setTranslate:K.setTranslate.bind(this),setTransition:K.setTransition.bind(this)}})},on:{beforeInit:function(){var e=this;if("flip"===e.params.effect){e.classNames.push(e.params.containerModifierClass+"flip"),e.classNames.push(e.params.containerModifierClass+"3d");var t={slidesPerView:1,slidesPerColumn:1,slidesPerGroup:1,watchSlidesProgress:!0,spaceBetween:0,virtualTranslate:!0};ee.extend(e.params,t),ee.extend(e.originalParams,t)}},setTranslate:function(){"flip"===this.params.effect&&this.flipEffect.setTranslate()},setTransition:function(e){"flip"===this.params.effect&&this.flipEffect.setTransition(e)}}},{name:"effect-coverflow",params:{coverflowEffect:{rotate:50,stretch:0,depth:100,modifier:1,slideShadows:!0}},create:function(){ee.extend(this,{coverflowEffect:{setTranslate:_.setTranslate.bind(this),setTransition:_.setTransition.bind(this)}})},on:{beforeInit:function(){var e=this;"coverflow"===e.params.effect&&(e.classNames.push(e.params.containerModifierClass+"coverflow"),e.classNames.push(e.params.containerModifierClass+"3d"),e.params.watchSlidesProgress=!0,e.originalParams.watchSlidesProgress=!0)},setTranslate:function(){"coverflow"===this.params.effect&&this.coverflowEffect.setTranslate()},setTransition:function(e){"coverflow"===this.params.effect&&this.coverflowEffect.setTransition(e)}}},{name:"thumbs",params:{thumbs:{swiper:null,slideThumbActiveClass:"swiper-slide-thumb-active",thumbsContainerClass:"swiper-container-thumbs"}},create:function(){ee.extend(this,{thumbs:{swiper:null,init:Z.init.bind(this),update:Z.update.bind(this),onThumbClick:Z.onThumbClick.bind(this)}})},on:{beforeInit:function(){var e=this.params.thumbs;e&&e.swiper&&(this.thumbs.init(),this.thumbs.update(!0))},slideChange:function(){this.thumbs.swiper&&this.thumbs.update()},update:function(){this.thumbs.swiper&&this.thumbs.update()},resize:function(){this.thumbs.swiper&&this.thumbs.update()},observerUpdate:function(){this.thumbs.swiper&&this.thumbs.update()},setTransition:function(e){var t=this.thumbs.swiper;t&&t.setTransition(e)},beforeDestroy:function(){var e=this.thumbs.swiper;e&&this.thumbs.swiperCreated&&e&&e.destroy()}}}];return void 0===T.use&&(T.use=T.Class.use,T.installModule=T.Class.installModule),T.use(Q),T}); +//# sourceMappingURL=swiper.min.js.map diff --git a/src/main/resources/templates/graduation/dist/js/viewer-unit.js b/src/main/resources/templates/graduation/dist/js/viewer-unit.js new file mode 100644 index 0000000000000000000000000000000000000000..9eb0697564a3992446355c10393fb4a7aff56804 --- /dev/null +++ b/src/main/resources/templates/graduation/dist/js/viewer-unit.js @@ -0,0 +1,49 @@ +var viewer = new Viewer(document.getElementById('image'), { + url: 'data-original', + fullscreen: false, + title: false, + movable: false, + toolbar: { + zoomIn: { + show: 1, + size: 'large' + }, + zoomOut: { + show: 1, + size: 'large' + }, + oneToOne: { + show: 1, + size: 'large' + }, + reset: { + show: 1, + size: 'large' + }, + prev: { + show: 1, + size: 'large' + }, + play: 1, + next: { + show: 1, + size: 'large' + }, + rotateLeft: { + show: 1, + size: 'large' + }, + rotateRight: { + show: 1, + size: 'large' + }, + flipHorizontal: { + show: 1, + size: 'large' + }, + flipVertical: { + show: 1, + size: 'large' + } + } +}); \ No newline at end of file diff --git a/src/main/resources/templates/graduation/dist/js/youth.js b/src/main/resources/templates/graduation/dist/js/youth.js new file mode 100644 index 0000000000000000000000000000000000000000..0573049c7ba90199ee478bc794843d3c32973596 --- /dev/null +++ b/src/main/resources/templates/graduation/dist/js/youth.js @@ -0,0 +1,173 @@ +var swiper = new Swiper('.swiper-container', { + slidesPerView: 1, + spaceBetween: 0, + loop: true, + speed:333, + //effect : 'fade', + pagination: { + el: '.swiper-pagination', + clickable: true, + //type: 'fraction', + //type: 'progressbar', + }, + navigation: { + nextEl: '.swiper-button-next', + prevEl: '.swiper-button-prev', + }, + autoplay : { + delay: 3000, + disableOnInteraction: false, + }, +}); + +;(function($, window, undefined) { + // outside the scope of the jQuery plugin to + // keep track of all dropdowns + var $allDropdowns = $(); + + // if instantlyCloseOthers is true, then it will instantly + // shut other nav items when a new one is hovered over + $.fn.dropdownHover = function(options) { + + // the element we really care about + // is the dropdown-toggle's parent + $allDropdowns = $allDropdowns.add(this.parent()); + + return this.each(function() { + var $this = $(this).parent(), + defaults = { + delay: 100, + instantlyCloseOthers: true + }, + data = { + delay: $(this).data('delay'), + instantlyCloseOthers: $(this).data('close-others') + }, + options = $.extend(true, {}, defaults, options, data), + timeout; + + $this.hover(function() { + if(options.instantlyCloseOthers === true) + $allDropdowns.removeClass('open'); + + window.clearTimeout(timeout); + $(this).addClass('open'); + }, function() { + timeout = window.setTimeout(function() { + $this.removeClass('open'); + }, options.delay); + }); + }); + }; + $('[data-hover="dropdown"]').dropdownHover(); +})(jQuery, this); + +if (!(/msie [6|7|8|9]/i.test(navigator.userAgent))){ + new WOW().init(); +}; +(function ($) { + "use strict"; + $(window).on('load', function () { + nhPreloader(); + bpMenuareaFixed(); + }); + function nhPreloader() { + if ($('#preloader').length) { + $('#preloader').fadeOut('slow', function () { + $(this).remove(); + }); + } + } + function bpMenuareaFixed() { + if ($('.header').length) { + $(window).on('scroll', function () { + if ($(window).scrollTop() > 0) { + $('.header').addClass('navbar-fixed-top'); + } else { + $('.header').removeClass('navbar-fixed-top'); + } + }); + } + } +}(jQuery)); +$(document).ready(function(){ + $('.counter-value').each(function(){ + $(this).prop('Counter',0).animate({ + Counter: $(this).text() + },{ + duration: 1000, + easing: 'swing', + step: function (now){ + $(this).text(Math.ceil(now).toLocaleString()); + } + }); + }); +}); +$(document).ready(function(){ + $('.counter-values').each(function(){ + $(this).prop('Counter',0).animate({ + Counter: $(this).text() + },{ + duration: 1000, + easing: 'swing', + step: function (now){ + $(this).text(Math.ceil(now).toLocaleString()); + } + }); + }); +}); +function pwdStrength(pwdStr) { + var hasNumber = 0; + var hasLetter = 0; + var hasSymbol = 0 + if (pwdStr.length >= 6) { + for (var i = 0; i < pwdStr.length; i++) { + var item = pwdStr[i]; + if (item >= '0' && item <= '9') { hasNumber = 1; } + else if ((item >= 'a' && item <= "z") || (item >= 'A' && item < "Z")) { hasLetter = 1; } + else { hasSymbol = 1; } + } + } + return hasLetter + hasNumber + hasSymbol; +} +//显示颜色 +function pwStrength(pwd) { + O_color = "#eeeeee"; + L_color = "#ff0000"; + M_color = "#ff9900"; + H_color = "#33cc00"; + if (pwd == null || pwd == '') { + Lcolor = Mcolor = Hcolor = O_color; + } + else { + S_level = pwdStrength(pwd); + switch (S_level) { + case 0: + Lcolor = Mcolor = Hcolor = O_color; + case 1: + Lcolor = L_color; + Mcolor = Hcolor = O_color; + break; + case 2: + Lcolor = L_color; + Mcolor = M_color; + Hcolor = O_color; + break; + default: + Lcolor = L_color; + Mcolor = M_color; + Hcolor = H_color; + } + } + document.getElementById("strength_L").style.background = Lcolor; + document.getElementById("strength_M").style.background = Mcolor; + document.getElementById("strength_H").style.background = Hcolor; + return; +} +$("#password").keyup(function(e){ + pwStrength($(e.target).val()); +}); + +var text =' Graduation博客主题 v1 '; +var youth ='%c' + text; +console.log('\n' + ' %c (づ ̄ ³ ̄)づヾ 作者:Logan ' + youth + ' ' + '\n' + '\n', 'color: #fadfa3; background: #030307; padding:5px 0;', 'background: #fadfa3; padding:5px 0;'); diff --git a/src/main/resources/templates/graduation/dist/viewer/css/viewer.css b/src/main/resources/templates/graduation/dist/viewer/css/viewer.css new file mode 100644 index 0000000000000000000000000000000000000000..c1f4b9108adf3b5e6d3d30c97d13c71411fb5605 --- /dev/null +++ b/src/main/resources/templates/graduation/dist/viewer/css/viewer.css @@ -0,0 +1,451 @@ +/*! + * Viewer.js v1.3.3 + * https://fengyuanchen.github.io/viewerjs + * + * Copyright 2015-present Chen Fengyuan + * Released under the MIT license + * + * Date: 2019-04-06T14:06:24.626Z + */ + +.viewer-zoom-in::before, +.viewer-zoom-out::before, +.viewer-one-to-one::before, +.viewer-reset::before, +.viewer-prev::before, +.viewer-play::before, +.viewer-next::before, +.viewer-rotate-left::before, +.viewer-rotate-right::before, +.viewer-flip-horizontal::before, +.viewer-flip-vertical::before, +.viewer-fullscreen::before, +.viewer-fullscreen-exit::before, +.viewer-close::before { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAAUCAYAAABWOyJDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAQPSURBVHic7Zs/iFxVFMa/0U2UaJGksUgnIVhYxVhpjDbZCBmLdAYECxsRFBTUamcXUiSNncgKQbSxsxH8gzAP3FU2jY0kKKJNiiiIghFlccnP4p3nPCdv3p9778vsLOcHB2bfveeb7955c3jvvNkBIMdxnD64a94GHMfZu3iBcRynN7zAOI7TG15gHCeeNUkr8zaxG2lbYDYsdgMbktBsP03jdQwljSXdtBhLOmtjowC9Mg9L+knSlcD8TNKpSA9lBpK2JF2VdDSR5n5J64m0qli399hNFMUlpshQii5jbXTbHGviB0nLNeNDSd9VO4A2UdB2fp+x0eCnaXxWXGA2X0au/3HgN9P4LFCjIANOJdrLr0zzZ+BEpNYDwKbpnQMeAw4m8HjQtM6Z9qa917zPQwFr3M5KgA6J5rTJCdFZJj9/lyvGhsDvwFNVuV2MhhjrK6b9bFiE+j1r87eBl4HDwCF7/U/k+ofAX5b/EXBv5JoLMuILzf3Ap6Z3EzgdqHMCuF7hcQf4HDgeoHnccncqdK/TvSDWffFXI/exICY/xZyqc6XLWF1UFZna4gJ7q8BsRvgd2/xXpo6P+D9dfT7PpECtA3cnWPM0GXGFZh/wgWltA+cDNC7X+AP4GzjZQe+k5dRxuYPeiuXU7e1qwLpDz7dFjXKRaSwuMLvAlG8zZlG+YmiK1HoFqT7wP2z+4Q45TfEGcMt01xLoNZEBTwRqD4BLpnMLeC1A41UmVxsXgXeBayV/Wx20rpTyrpnWRft7p6O/FdqzGrDukPNtkaMoMo3FBdBSQMOnYBCReyf05s126fU9ytfX98+mY54Kxnp7S9K3kj6U9KYdG0h6UdLbkh7poFXMfUnSOyVvL0h6VtIXHbS6nOP+s/Zm9mvyXW1uuC9ohZ72E9uDmXWLJOB1GxsH+DxPftsB8B6wlGDN02TAkxG6+4D3TWsbeC5CS8CDFce+AW500LhhOW2020TRjK3b21HEmgti9m0RonxbdMZeVzV+/4tF3cBpP7E9mKHNL5q8h5g0eYsCMQz0epq8gQrwMXAgcs0FGXGFRcB9wCemF9PkbYqM/Bas7fxLwNeJPdTdpo4itQti8lPMqTpXuozVRVXPpbHI3KkNTB1NfkL81j2mvhDp91HgV9MKuRIqrykj3WPq4rHyL+axj8/qGPmTqi6F9YDlHOvJU6oYcTsh/TYSzWmTE6JT19CtLTJt32D6CmHe0eQn1O8z5AXgT4sx4Vcu0/EQecMydB8z0hUWkTd2t4CrwNEePqMBcAR4mrBbwyXLPWJa8zrXmmLEhNBmfpkuY2102xxrih+pb+ieAb6vGhuA97UcJ5KR8gZ77K+99xxeYBzH6Q3/Z0fHcXrDC4zjOL3hBcZxnN74F+zlvXFWXF9PAAAAAElFTkSuQmCC'); + background-repeat: no-repeat; + background-size: 280px; + color: transparent; + display: block; + font-size: 0; + height: 20px; + line-height: 0; + width: 20px; +} + +.viewer-zoom-in::before { + background-position: 0 0; + content: 'Zoom In'; +} + +.viewer-zoom-out::before { + background-position: -20px 0; + content: 'Zoom Out'; +} + +.viewer-one-to-one::before { + background-position: -40px 0; + content: 'One to One'; +} + +.viewer-reset::before { + background-position: -60px 0; + content: 'Reset'; +} + +.viewer-prev::before { + background-position: -80px 0; + content: 'Previous'; +} + +.viewer-play::before { + background-position: -100px 0; + content: 'Play'; +} + +.viewer-next::before { + background-position: -120px 0; + content: 'Next'; +} + +.viewer-rotate-left::before { + background-position: -140px 0; + content: 'Rotate Left'; +} + +.viewer-rotate-right::before { + background-position: -160px 0; + content: 'Rotate Right'; +} + +.viewer-flip-horizontal::before { + background-position: -180px 0; + content: 'Flip Horizontal'; +} + +.viewer-flip-vertical::before { + background-position: -200px 0; + content: 'Flip Vertical'; +} + +.viewer-fullscreen::before { + background-position: -220px 0; + content: 'Enter Full Screen'; +} + +.viewer-fullscreen-exit::before { + background-position: -240px 0; + content: 'Exit Full Screen'; +} + +.viewer-close::before { + background-position: -260px 0; + content: 'Close'; +} + +.viewer-container { + bottom: 0; + direction: ltr; + font-size: 0; + left: 0; + line-height: 0; + overflow: hidden; + position: absolute; + right: 0; + -webkit-tap-highlight-color: transparent; + top: 0; + -ms-touch-action: none; + touch-action: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.viewer-container::selection, +.viewer-container *::selection { + background-color: transparent; +} + +.viewer-container img { + display: block; + height: auto; + max-height: none !important; + max-width: none !important; + min-height: 0 !important; + min-width: 0 !important; + width: 100%; +} + +.viewer-canvas { + bottom: 0; + left: 0; + overflow: hidden; + position: absolute; + right: 0; + top: 0; +} + +.viewer-canvas > img { + height: auto; + margin: 15px auto; + max-width: 90% !important; + width: auto; +} + +.viewer-footer { + bottom: 0; + left: 0; + overflow: hidden; + position: absolute; + right: 0; + text-align: center; +} + +.viewer-navbar { + background-color: rgba(0, 0, 0, 0.5); + overflow: hidden; +} + +.viewer-list { + -webkit-box-sizing: content-box; + box-sizing: content-box; + height: 50px; + margin: 0; + overflow: hidden; + padding: 1px 0; +} + +.viewer-list > li { + color: transparent; + cursor: pointer; + float: left; + font-size: 0; + height: 50px; + line-height: 0; + opacity: 0.5; + overflow: hidden; + -webkit-transition: opacity 0.15s; + transition: opacity 0.15s; + width: 30px; +} + +.viewer-list > li:hover { + opacity: 0.75; +} + +.viewer-list > li + li { + margin-left: 1px; +} + +.viewer-list > .viewer-loading { + position: relative; +} + +.viewer-list > .viewer-loading::after { + border-width: 2px; + height: 20px; + margin-left: -10px; + margin-top: -10px; + width: 20px; +} + +.viewer-list > .viewer-active, +.viewer-list > .viewer-active:hover { + opacity: 1; +} + +.viewer-player { + background-color: #000; + bottom: 0; + cursor: none; + display: none; + left: 0; + position: absolute; + right: 0; + top: 0; +} + +.viewer-player > img { + left: 0; + position: absolute; + top: 0; +} + +.viewer-toolbar > ul { + display: inline-block; + margin: 0 auto 5px; + overflow: hidden; + padding: 3px 0; +} + +.viewer-toolbar > ul > li { + background-color: rgba(0, 0, 0, 0.5); + border-radius: 50%; + cursor: pointer; + float: left; + height: 24px; + overflow: hidden; + -webkit-transition: background-color 0.15s; + transition: background-color 0.15s; + width: 24px; +} + +.viewer-toolbar > ul > li:hover { + background-color: rgba(0, 0, 0, 0.8); +} + +.viewer-toolbar > ul > li::before { + margin: 2px; +} + +.viewer-toolbar > ul > li + li { + margin-left: 1px; +} + +.viewer-toolbar > ul > .viewer-small { + height: 18px; + margin-bottom: 3px; + margin-top: 3px; + width: 18px; +} + +.viewer-toolbar > ul > .viewer-small::before { + margin: -1px; +} + +.viewer-toolbar > ul > .viewer-large { + height: 30px; + margin-bottom: -3px; + margin-top: -3px; + width: 30px; +} + +.viewer-toolbar > ul > .viewer-large::before { + margin: 5px; +} + +.viewer-tooltip { + background-color: rgba(0, 0, 0, 0.8); + border-radius: 10px; + color: #fff; + display: none; + font-size: 12px; + height: 20px; + left: 50%; + line-height: 20px; + margin-left: -25px; + margin-top: -10px; + position: absolute; + text-align: center; + top: 50%; + width: 50px; +} + +.viewer-title { + color: #ccc; + display: inline-block; + font-size: 12px; + line-height: 1; + margin: 0 5% 5px; + max-width: 90%; + opacity: 0.8; + overflow: hidden; + text-overflow: ellipsis; + -webkit-transition: opacity 0.15s; + transition: opacity 0.15s; + white-space: nowrap; +} + +.viewer-title:hover { + opacity: 1; +} + +.viewer-button { + background-color: rgba(0, 0, 0, 0.5); + border-radius: 50%; + cursor: pointer; + height: 80px; + overflow: hidden; + position: absolute; + right: -40px; + top: -40px; + -webkit-transition: background-color 0.15s; + transition: background-color 0.15s; + width: 80px; +} + +.viewer-button:focus, +.viewer-button:hover { + background-color: rgba(0, 0, 0, 0.8); +} + +.viewer-button::before { + bottom: 15px; + left: 15px; + position: absolute; +} + +.viewer-fixed { + position: fixed; +} + +.viewer-open { + overflow: hidden; +} + +.viewer-show { + display: block; +} + +.viewer-hide { + display: none; +} + +.viewer-backdrop { + background-color: rgba(0, 0, 0, 0.5); +} + +.viewer-invisible { + visibility: hidden; +} + +.viewer-move { + cursor: move; + cursor: -webkit-grab; + cursor: grab; +} + +.viewer-fade { + opacity: 0; +} + +.viewer-in { + opacity: 1; +} + +.viewer-transition { + -webkit-transition: all 0.3s; + transition: all 0.3s; +} + +@-webkit-keyframes viewer-spinner { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes viewer-spinner { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +.viewer-loading::after { + -webkit-animation: viewer-spinner 1s linear infinite; + animation: viewer-spinner 1s linear infinite; + border: 4px solid rgba(255, 255, 255, 0.1); + border-left-color: rgba(255, 255, 255, 0.5); + border-radius: 50%; + content: ''; + display: inline-block; + height: 40px; + left: 50%; + margin-left: -20px; + margin-top: -20px; + position: absolute; + top: 50%; + width: 40px; + z-index: 1; +} + +@media (max-width: 767px) { + .viewer-hide-xs-down { + display: none; + } +} + +@media (max-width: 991px) { + .viewer-hide-sm-down { + display: none; + } +} + +@media (max-width: 1199px) { + .viewer-hide-md-down { + display: none; + } +} diff --git a/src/main/resources/templates/graduation/dist/viewer/css/viewer.min.css b/src/main/resources/templates/graduation/dist/viewer/css/viewer.min.css new file mode 100644 index 0000000000000000000000000000000000000000..2d32acc4592b855892b245434979e44901d71e12 --- /dev/null +++ b/src/main/resources/templates/graduation/dist/viewer/css/viewer.min.css @@ -0,0 +1,9 @@ +/*! + * Viewer.js v1.3.3 + * https://fengyuanchen.github.io/viewerjs + * + * Copyright 2015-present Chen Fengyuan + * Released under the MIT license + * + * Date: 2019-04-06T14:06:24.626Z + */.viewer-close:before,.viewer-flip-horizontal:before,.viewer-flip-vertical:before,.viewer-fullscreen-exit:before,.viewer-fullscreen:before,.viewer-next:before,.viewer-one-to-one:before,.viewer-play:before,.viewer-prev:before,.viewer-reset:before,.viewer-rotate-left:before,.viewer-rotate-right:before,.viewer-zoom-in:before,.viewer-zoom-out:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAAUCAYAAABWOyJDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAQPSURBVHic7Zs/iFxVFMa/0U2UaJGksUgnIVhYxVhpjDbZCBmLdAYECxsRFBTUamcXUiSNncgKQbSxsxH8gzAP3FU2jY0kKKJNiiiIghFlccnP4p3nPCdv3p9778vsLOcHB2bfveeb7955c3jvvNkBIMdxnD64a94GHMfZu3iBcRynN7zAOI7TG15gHCeeNUkr8zaxG2lbYDYsdgMbktBsP03jdQwljSXdtBhLOmtjowC9Mg9L+knSlcD8TNKpSA9lBpK2JF2VdDSR5n5J64m0qli399hNFMUlpshQii5jbXTbHGviB0nLNeNDSd9VO4A2UdB2fp+x0eCnaXxWXGA2X0au/3HgN9P4LFCjIANOJdrLr0zzZ+BEpNYDwKbpnQMeAw4m8HjQtM6Z9qa917zPQwFr3M5KgA6J5rTJCdFZJj9/lyvGhsDvwFNVuV2MhhjrK6b9bFiE+j1r87eBl4HDwCF7/U/k+ofAX5b/EXBv5JoLMuILzf3Ap6Z3EzgdqHMCuF7hcQf4HDgeoHnccncqdK/TvSDWffFXI/exICY/xZyqc6XLWF1UFZna4gJ7q8BsRvgd2/xXpo6P+D9dfT7PpECtA3cnWPM0GXGFZh/wgWltA+cDNC7X+AP4GzjZQe+k5dRxuYPeiuXU7e1qwLpDz7dFjXKRaSwuMLvAlG8zZlG+YmiK1HoFqT7wP2z+4Q45TfEGcMt01xLoNZEBTwRqD4BLpnMLeC1A41UmVxsXgXeBayV/Wx20rpTyrpnWRft7p6O/FdqzGrDukPNtkaMoMo3FBdBSQMOnYBCReyf05s126fU9ytfX98+mY54Kxnp7S9K3kj6U9KYdG0h6UdLbkh7poFXMfUnSOyVvL0h6VtIXHbS6nOP+s/Zm9mvyXW1uuC9ohZ72E9uDmXWLJOB1GxsH+DxPftsB8B6wlGDN02TAkxG6+4D3TWsbeC5CS8CDFce+AW500LhhOW2020TRjK3b21HEmgti9m0RonxbdMZeVzV+/4tF3cBpP7E9mKHNL5q8h5g0eYsCMQz0epq8gQrwMXAgcs0FGXGFRcB9wCemF9PkbYqM/Bas7fxLwNeJPdTdpo4itQti8lPMqTpXuozVRVXPpbHI3KkNTB1NfkL81j2mvhDp91HgV9MKuRIqrykj3WPq4rHyL+axj8/qGPmTqi6F9YDlHOvJU6oYcTsh/TYSzWmTE6JT19CtLTJt32D6CmHe0eQn1O8z5AXgT4sx4Vcu0/EQecMydB8z0hUWkTd2t4CrwNEePqMBcAR4mrBbwyXLPWJa8zrXmmLEhNBmfpkuY2102xxrih+pb+ieAb6vGhuA97UcJ5KR8gZ77K+99xxeYBzH6Q3/Z0fHcXrDC4zjOL3hBcZxnN74F+zlvXFWXF9PAAAAAElFTkSuQmCC");background-repeat:no-repeat;background-size:280px;color:transparent;display:block;font-size:0;height:20px;line-height:0;width:20px}.viewer-zoom-in:before{background-position:0 0;content:"Zoom In"}.viewer-zoom-out:before{background-position:-20px 0;content:"Zoom Out"}.viewer-one-to-one:before{background-position:-40px 0;content:"One to One"}.viewer-reset:before{background-position:-60px 0;content:"Reset"}.viewer-prev:before{background-position:-80px 0;content:"Previous"}.viewer-play:before{background-position:-100px 0;content:"Play"}.viewer-next:before{background-position:-120px 0;content:"Next"}.viewer-rotate-left:before{background-position:-140px 0;content:"Rotate Left"}.viewer-rotate-right:before{background-position:-160px 0;content:"Rotate Right"}.viewer-flip-horizontal:before{background-position:-180px 0;content:"Flip Horizontal"}.viewer-flip-vertical:before{background-position:-200px 0;content:"Flip Vertical"}.viewer-fullscreen:before{background-position:-220px 0;content:"Enter Full Screen"}.viewer-fullscreen-exit:before{background-position:-240px 0;content:"Exit Full Screen"}.viewer-close:before{background-position:-260px 0;content:"Close"}.viewer-container{bottom:0;direction:ltr;font-size:0;left:0;line-height:0;overflow:hidden;position:absolute;right:0;-webkit-tap-highlight-color:transparent;top:0;-ms-touch-action:none;touch-action:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.viewer-container::selection,.viewer-container ::selection{background-color:transparent}.viewer-container img{display:block;height:auto;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.viewer-canvas{bottom:0;left:0;overflow:hidden;position:absolute;right:0;top:0}.viewer-canvas>img{height:auto;margin:15px auto;max-width:90%!important;width:auto}.viewer-footer{bottom:0;left:0;overflow:hidden;position:absolute;right:0;text-align:center}.viewer-navbar{background-color:rgba(0,0,0,.5);overflow:hidden}.viewer-list{-webkit-box-sizing:content-box;box-sizing:content-box;height:50px;margin:0;overflow:hidden;padding:1px 0}.viewer-list>li{color:transparent;cursor:pointer;float:left;font-size:0;height:50px;line-height:0;opacity:.5;overflow:hidden;-webkit-transition:opacity .15s;transition:opacity .15s;width:30px}.viewer-list>li:hover{opacity:.75}.viewer-list>li+li{margin-left:1px}.viewer-list>.viewer-loading{position:relative}.viewer-list>.viewer-loading:after{border-width:2px;height:20px;margin-left:-10px;margin-top:-10px;width:20px}.viewer-list>.viewer-active,.viewer-list>.viewer-active:hover{opacity:1}.viewer-player{background-color:#000;bottom:0;cursor:none;display:none;right:0}.viewer-player,.viewer-player>img{left:0;position:absolute;top:0}.viewer-toolbar>ul{display:inline-block;margin:0 auto 5px;overflow:hidden;padding:3px 0}.viewer-toolbar>ul>li{background-color:rgba(0,0,0,.5);border-radius:50%;cursor:pointer;float:left;height:24px;overflow:hidden;-webkit-transition:background-color .15s;transition:background-color .15s;width:24px}.viewer-toolbar>ul>li:hover{background-color:rgba(0,0,0,.8)}.viewer-toolbar>ul>li:before{margin:2px}.viewer-toolbar>ul>li+li{margin-left:1px}.viewer-toolbar>ul>.viewer-small{height:18px;margin-bottom:3px;margin-top:3px;width:18px}.viewer-toolbar>ul>.viewer-small:before{margin:-1px}.viewer-toolbar>ul>.viewer-large{height:30px;margin-bottom:-3px;margin-top:-3px;width:30px}.viewer-toolbar>ul>.viewer-large:before{margin:5px}.viewer-tooltip{background-color:rgba(0,0,0,.8);border-radius:10px;color:#fff;display:none;font-size:12px;height:20px;left:50%;line-height:20px;margin-left:-25px;margin-top:-10px;position:absolute;text-align:center;top:50%;width:50px}.viewer-title{color:#ccc;display:inline-block;font-size:12px;line-height:1;margin:0 5% 5px;max-width:90%;opacity:.8;overflow:hidden;text-overflow:ellipsis;-webkit-transition:opacity .15s;transition:opacity .15s;white-space:nowrap}.viewer-title:hover{opacity:1}.viewer-button{background-color:rgba(0,0,0,.5);border-radius:50%;cursor:pointer;height:80px;overflow:hidden;position:absolute;right:-40px;top:-40px;-webkit-transition:background-color .15s;transition:background-color .15s;width:80px}.viewer-button:focus,.viewer-button:hover{background-color:rgba(0,0,0,.8)}.viewer-button:before{bottom:15px;left:15px;position:absolute}.viewer-fixed{position:fixed}.viewer-open{overflow:hidden}.viewer-show{display:block}.viewer-hide{display:none}.viewer-backdrop{background-color:rgba(0,0,0,.5)}.viewer-invisible{visibility:hidden}.viewer-move{cursor:move;cursor:-webkit-grab;cursor:grab}.viewer-fade{opacity:0}.viewer-in{opacity:1}.viewer-transition{-webkit-transition:all .3s;transition:all .3s}@-webkit-keyframes viewer-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes viewer-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.viewer-loading:after{-webkit-animation:viewer-spinner 1s linear infinite;animation:viewer-spinner 1s linear infinite;border:4px solid hsla(0,0%,100%,.1);border-left-color:hsla(0,0%,100%,.5);border-radius:50%;content:"";display:inline-block;height:40px;left:50%;margin-left:-20px;margin-top:-20px;position:absolute;top:50%;width:40px;z-index:1}@media (max-width:767px){.viewer-hide-xs-down{display:none}}@media (max-width:991px){.viewer-hide-sm-down{display:none}}@media (max-width:1199px){.viewer-hide-md-down{display:none}} \ No newline at end of file diff --git a/src/main/resources/templates/graduation/dist/viewer/js/viewer.common.js b/src/main/resources/templates/graduation/dist/viewer/js/viewer.common.js new file mode 100644 index 0000000000000000000000000000000000000000..a9546a97e07d4c5066ed9dacfc359932ffe26fad --- /dev/null +++ b/src/main/resources/templates/graduation/dist/viewer/js/viewer.common.js @@ -0,0 +1,3016 @@ +/*! + * Viewer.js v1.3.3 + * https://fengyuanchen.github.io/viewerjs + * + * Copyright 2015-present Chen Fengyuan + * Released under the MIT license + * + * Date: 2019-04-06T14:06:28.301Z + */ + +'use strict'; + +function _typeof(obj) { + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function (obj) { + return typeof obj; + }; + } else { + _typeof = function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + } + + return _typeof(obj); +} + +function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +} + +function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } +} + +function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; +} + +var DEFAULTS = { + /** + * Enable a modal backdrop, specify `static` for a backdrop + * which doesn't close the modal on click. + * @type {boolean} + */ + backdrop: true, + + /** + * Show the button on the top-right of the viewer. + * @type {boolean} + */ + button: true, + + /** + * Show the navbar. + * @type {boolean | number} + */ + navbar: true, + + /** + * Specify the visibility and the content of the title. + * @type {boolean | number | Function | Array} + */ + title: true, + + /** + * Show the toolbar. + * @type {boolean | number | Object} + */ + toolbar: true, + + /** + * Custom class name(s) to add to the viewer's root element. + * @type {string} + */ + className: '', + + /** + * Define where to put the viewer in modal mode. + * @type {string | Element} + */ + container: 'body', + + /** + * Filter the images for viewing. Return true if the image is viewable. + * @type {Function} + */ + filter: null, + + /** + * Enable to request fullscreen when play. + * @type {boolean} + */ + fullscreen: true, + + /** + * Define the initial index of image for viewing. + * @type {number} + */ + initialViewIndex: 0, + + /** + * Enable inline mode. + * @type {boolean} + */ + inline: false, + + /** + * The amount of time to delay between automatically cycling an image when playing. + * @type {number} + */ + interval: 5000, + + /** + * Enable keyboard support. + * @type {boolean} + */ + keyboard: true, + + /** + * Indicate if show a loading spinner when load image or not. + * @type {boolean} + */ + loading: true, + + /** + * Indicate if enable loop viewing or not. + * @type {boolean} + */ + loop: true, + + /** + * Min width of the viewer in inline mode. + * @type {number} + */ + minWidth: 200, + + /** + * Min height of the viewer in inline mode. + * @type {number} + */ + minHeight: 100, + + /** + * Enable to move the image. + * @type {boolean} + */ + movable: true, + + /** + * Enable to zoom the image. + * @type {boolean} + */ + zoomable: true, + + /** + * Enable to rotate the image. + * @type {boolean} + */ + rotatable: true, + + /** + * Enable to scale the image. + * @type {boolean} + */ + scalable: true, + + /** + * Indicate if toggle the image size between its natural size + * and initial size when double click on the image or not. + * @type {boolean} + */ + toggleOnDblclick: true, + + /** + * Show the tooltip with image ratio (percentage) when zoom in or zoom out. + * @type {boolean} + */ + tooltip: true, + + /** + * Enable CSS3 Transition for some special elements. + * @type {boolean} + */ + transition: true, + + /** + * Define the CSS `z-index` value of viewer in modal mode. + * @type {number} + */ + zIndex: 2015, + + /** + * Define the CSS `z-index` value of viewer in inline mode. + * @type {number} + */ + zIndexInline: 0, + + /** + * Define the ratio when zoom the image by wheeling mouse. + * @type {number} + */ + zoomRatio: 0.1, + + /** + * Define the min ratio of the image when zoom out. + * @type {number} + */ + minZoomRatio: 0.01, + + /** + * Define the max ratio of the image when zoom in. + * @type {number} + */ + maxZoomRatio: 100, + + /** + * Define where to get the original image URL for viewing. + * @type {string | Function} + */ + url: 'src', + + /** + * Event shortcuts. + * @type {Function} + */ + ready: null, + show: null, + shown: null, + hide: null, + hidden: null, + view: null, + viewed: null, + zoom: null, + zoomed: null +}; + +var TEMPLATE = '
      ' + '
      ' + '' + '
      ' + '
      ' + '
      ' + '
      '; + +var IS_BROWSER = typeof window !== 'undefined'; +var WINDOW = IS_BROWSER ? window : {}; +var IS_TOUCH_DEVICE = IS_BROWSER ? 'ontouchstart' in WINDOW.document.documentElement : false; +var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false; +var NAMESPACE = 'viewer'; // Actions + +var ACTION_MOVE = 'move'; +var ACTION_SWITCH = 'switch'; +var ACTION_ZOOM = 'zoom'; // Classes + +var CLASS_ACTIVE = "".concat(NAMESPACE, "-active"); +var CLASS_CLOSE = "".concat(NAMESPACE, "-close"); +var CLASS_FADE = "".concat(NAMESPACE, "-fade"); +var CLASS_FIXED = "".concat(NAMESPACE, "-fixed"); +var CLASS_FULLSCREEN = "".concat(NAMESPACE, "-fullscreen"); +var CLASS_FULLSCREEN_EXIT = "".concat(NAMESPACE, "-fullscreen-exit"); +var CLASS_HIDE = "".concat(NAMESPACE, "-hide"); +var CLASS_HIDE_MD_DOWN = "".concat(NAMESPACE, "-hide-md-down"); +var CLASS_HIDE_SM_DOWN = "".concat(NAMESPACE, "-hide-sm-down"); +var CLASS_HIDE_XS_DOWN = "".concat(NAMESPACE, "-hide-xs-down"); +var CLASS_IN = "".concat(NAMESPACE, "-in"); +var CLASS_INVISIBLE = "".concat(NAMESPACE, "-invisible"); +var CLASS_LOADING = "".concat(NAMESPACE, "-loading"); +var CLASS_MOVE = "".concat(NAMESPACE, "-move"); +var CLASS_OPEN = "".concat(NAMESPACE, "-open"); +var CLASS_SHOW = "".concat(NAMESPACE, "-show"); +var CLASS_TRANSITION = "".concat(NAMESPACE, "-transition"); // Events + +var EVENT_CLICK = 'click'; +var EVENT_DBLCLICK = 'dblclick'; +var EVENT_DRAG_START = 'dragstart'; +var EVENT_HIDDEN = 'hidden'; +var EVENT_HIDE = 'hide'; +var EVENT_KEY_DOWN = 'keydown'; +var EVENT_LOAD = 'load'; +var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown'; +var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove'; +var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup'; +var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START; +var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE; +var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END; +var EVENT_READY = 'ready'; +var EVENT_RESIZE = 'resize'; +var EVENT_SHOW = 'show'; +var EVENT_SHOWN = 'shown'; +var EVENT_TRANSITION_END = 'transitionend'; +var EVENT_VIEW = 'view'; +var EVENT_VIEWED = 'viewed'; +var EVENT_WHEEL = 'wheel'; +var EVENT_ZOOM = 'zoom'; +var EVENT_ZOOMED = 'zoomed'; // Data keys + +var DATA_ACTION = "".concat(NAMESPACE, "Action"); // RegExps + +var REGEXP_SPACES = /\s\s*/; // Misc + +var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical']; + +/** + * Check if the given value is a string. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a string, else `false`. + */ + +function isString(value) { + return typeof value === 'string'; +} +/** + * Check if the given value is not a number. + */ + +var isNaN = Number.isNaN || WINDOW.isNaN; +/** + * Check if the given value is a number. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a number, else `false`. + */ + +function isNumber(value) { + return typeof value === 'number' && !isNaN(value); +} +/** + * Check if the given value is undefined. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is undefined, else `false`. + */ + +function isUndefined(value) { + return typeof value === 'undefined'; +} +/** + * Check if the given value is an object. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is an object, else `false`. + */ + +function isObject(value) { + return _typeof(value) === 'object' && value !== null; +} +var hasOwnProperty = Object.prototype.hasOwnProperty; +/** + * Check if the given value is a plain object. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a plain object, else `false`. + */ + +function isPlainObject(value) { + if (!isObject(value)) { + return false; + } + + try { + var _constructor = value.constructor; + var prototype = _constructor.prototype; + return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf'); + } catch (error) { + return false; + } +} +/** + * Check if the given value is a function. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a function, else `false`. + */ + +function isFunction(value) { + return typeof value === 'function'; +} +/** + * Iterate the given data. + * @param {*} data - The data to iterate. + * @param {Function} callback - The process function for each element. + * @returns {*} The original data. + */ + +function forEach(data, callback) { + if (data && isFunction(callback)) { + if (Array.isArray(data) || isNumber(data.length) + /* array-like */ + ) { + var length = data.length; + var i; + + for (i = 0; i < length; i += 1) { + if (callback.call(data, data[i], i, data) === false) { + break; + } + } + } else if (isObject(data)) { + Object.keys(data).forEach(function (key) { + callback.call(data, data[key], key, data); + }); + } + } + + return data; +} +/** + * Extend the given object. + * @param {*} obj - The object to be extended. + * @param {*} args - The rest objects which will be merged to the first object. + * @returns {Object} The extended object. + */ + +var assign = Object.assign || function assign(obj) { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + if (isObject(obj) && args.length > 0) { + args.forEach(function (arg) { + if (isObject(arg)) { + Object.keys(arg).forEach(function (key) { + obj[key] = arg[key]; + }); + } + }); + } + + return obj; +}; +var REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/; +/** + * Apply styles to the given element. + * @param {Element} element - The target element. + * @param {Object} styles - The styles for applying. + */ + +function setStyle(element, styles) { + var style = element.style; + forEach(styles, function (value, property) { + if (REGEXP_SUFFIX.test(property) && isNumber(value)) { + value += 'px'; + } + + style[property] = value; + }); +} +/** + * Check if the given element has a special class. + * @param {Element} element - The element to check. + * @param {string} value - The class to search. + * @returns {boolean} Returns `true` if the special class was found. + */ + +function hasClass(element, value) { + return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1; +} +/** + * Add classes to the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be added. + */ + +function addClass(element, value) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + addClass(elem, value); + }); + return; + } + + if (element.classList) { + element.classList.add(value); + return; + } + + var className = element.className.trim(); + + if (!className) { + element.className = value; + } else if (className.indexOf(value) < 0) { + element.className = "".concat(className, " ").concat(value); + } +} +/** + * Remove classes from the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be removed. + */ + +function removeClass(element, value) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + removeClass(elem, value); + }); + return; + } + + if (element.classList) { + element.classList.remove(value); + return; + } + + if (element.className.indexOf(value) >= 0) { + element.className = element.className.replace(value, ''); + } +} +/** + * Add or remove classes from the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be toggled. + * @param {boolean} added - Add only. + */ + +function toggleClass(element, value, added) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + toggleClass(elem, value, added); + }); + return; + } // IE10-11 doesn't support the second parameter of `classList.toggle` + + + if (added) { + addClass(element, value); + } else { + removeClass(element, value); + } +} +var REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g; +/** + * Transform the given string from camelCase to kebab-case + * @param {string} value - The value to transform. + * @returns {string} The transformed value. + */ + +function hyphenate(value) { + return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase(); +} +/** + * Get data from the given element. + * @param {Element} element - The target element. + * @param {string} name - The data key to get. + * @returns {string} The data value. + */ + +function getData(element, name) { + if (isObject(element[name])) { + return element[name]; + } + + if (element.dataset) { + return element.dataset[name]; + } + + return element.getAttribute("data-".concat(hyphenate(name))); +} +/** + * Set data to the given element. + * @param {Element} element - The target element. + * @param {string} name - The data key to set. + * @param {string} data - The data value. + */ + +function setData(element, name, data) { + if (isObject(data)) { + element[name] = data; + } else if (element.dataset) { + element.dataset[name] = data; + } else { + element.setAttribute("data-".concat(hyphenate(name)), data); + } +} + +var onceSupported = function () { + var supported = false; + + if (IS_BROWSER) { + var once = false; + + var listener = function listener() {}; + + var options = Object.defineProperty({}, 'once', { + get: function get() { + supported = true; + return once; + }, + + /** + * This setter can fix a `TypeError` in strict mode + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only} + * @param {boolean} value - The value to set + */ + set: function set(value) { + once = value; + } + }); + WINDOW.addEventListener('test', listener, options); + WINDOW.removeEventListener('test', listener, options); + } + + return supported; +}(); +/** + * Remove event listener from the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Function} listener - The event listener. + * @param {Object} options - The event options. + */ + + +function removeListener(element, type, listener) { + var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + var handler = listener; + type.trim().split(REGEXP_SPACES).forEach(function (event) { + if (!onceSupported) { + var listeners = element.listeners; + + if (listeners && listeners[event] && listeners[event][listener]) { + handler = listeners[event][listener]; + delete listeners[event][listener]; + + if (Object.keys(listeners[event]).length === 0) { + delete listeners[event]; + } + + if (Object.keys(listeners).length === 0) { + delete element.listeners; + } + } + } + + element.removeEventListener(event, handler, options); + }); +} +/** + * Add event listener to the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Function} listener - The event listener. + * @param {Object} options - The event options. + */ + +function addListener(element, type, listener) { + var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + var _handler = listener; + type.trim().split(REGEXP_SPACES).forEach(function (event) { + if (options.once && !onceSupported) { + var _element$listeners = element.listeners, + listeners = _element$listeners === void 0 ? {} : _element$listeners; + + _handler = function handler() { + delete listeners[event][listener]; + element.removeEventListener(event, _handler, options); + + for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + listener.apply(element, args); + }; + + if (!listeners[event]) { + listeners[event] = {}; + } + + if (listeners[event][listener]) { + element.removeEventListener(event, listeners[event][listener], options); + } + + listeners[event][listener] = _handler; + element.listeners = listeners; + } + + element.addEventListener(event, _handler, options); + }); +} +/** + * Dispatch event on the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Object} data - The additional event data. + * @returns {boolean} Indicate if the event is default prevented or not. + */ + +function dispatchEvent(element, type, data) { + var event; // Event and CustomEvent on IE9-11 are global objects, not constructors + + if (isFunction(Event) && isFunction(CustomEvent)) { + event = new CustomEvent(type, { + detail: data, + bubbles: true, + cancelable: true + }); + } else { + event = document.createEvent('CustomEvent'); + event.initCustomEvent(type, true, true, data); + } + + return element.dispatchEvent(event); +} +/** + * Get the offset base on the document. + * @param {Element} element - The target element. + * @returns {Object} The offset data. + */ + +function getOffset(element) { + var box = element.getBoundingClientRect(); + return { + left: box.left + (window.pageXOffset - document.documentElement.clientLeft), + top: box.top + (window.pageYOffset - document.documentElement.clientTop) + }; +} +/** + * Get transforms base on the given object. + * @param {Object} obj - The target object. + * @returns {string} A string contains transform values. + */ + +function getTransforms(_ref) { + var rotate = _ref.rotate, + scaleX = _ref.scaleX, + scaleY = _ref.scaleY, + translateX = _ref.translateX, + translateY = _ref.translateY; + var values = []; + + if (isNumber(translateX) && translateX !== 0) { + values.push("translateX(".concat(translateX, "px)")); + } + + if (isNumber(translateY) && translateY !== 0) { + values.push("translateY(".concat(translateY, "px)")); + } // Rotate should come first before scale to match orientation transform + + + if (isNumber(rotate) && rotate !== 0) { + values.push("rotate(".concat(rotate, "deg)")); + } + + if (isNumber(scaleX) && scaleX !== 1) { + values.push("scaleX(".concat(scaleX, ")")); + } + + if (isNumber(scaleY) && scaleY !== 1) { + values.push("scaleY(".concat(scaleY, ")")); + } + + var transform = values.length ? values.join(' ') : 'none'; + return { + WebkitTransform: transform, + msTransform: transform, + transform: transform + }; +} +/** + * Get an image name from an image url. + * @param {string} url - The target url. + * @example + * // picture.jpg + * getImageNameFromURL('http://domain.com/path/to/picture.jpg?size=1280×960') + * @returns {string} A string contains the image name. + */ + +function getImageNameFromURL(url) { + return isString(url) ? url.replace(/^.*\//, '').replace(/[?&#].*$/, '') : ''; +} +var IS_SAFARI = WINDOW.navigator && /(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(WINDOW.navigator.userAgent); +/** + * Get an image's natural sizes. + * @param {string} image - The target image. + * @param {Function} callback - The callback function. + * @returns {HTMLImageElement} The new image. + */ + +function getImageNaturalSizes(image, callback) { + var newImage = document.createElement('img'); // Modern browsers (except Safari) + + if (image.naturalWidth && !IS_SAFARI) { + callback(image.naturalWidth, image.naturalHeight); + return newImage; + } + + var body = document.body || document.documentElement; + + newImage.onload = function () { + callback(newImage.width, newImage.height); + + if (!IS_SAFARI) { + body.removeChild(newImage); + } + }; + + newImage.src = image.src; // iOS Safari will convert the image automatically + // with its orientation once append it into DOM + + if (!IS_SAFARI) { + newImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;'; + body.appendChild(newImage); + } + + return newImage; +} +/** + * Get the related class name of a responsive type number. + * @param {string} type - The responsive type. + * @returns {string} The related class name. + */ + +function getResponsiveClass(type) { + switch (type) { + case 2: + return CLASS_HIDE_XS_DOWN; + + case 3: + return CLASS_HIDE_SM_DOWN; + + case 4: + return CLASS_HIDE_MD_DOWN; + + default: + return ''; + } +} +/** + * Get the max ratio of a group of pointers. + * @param {string} pointers - The target pointers. + * @returns {number} The result ratio. + */ + +function getMaxZoomRatio(pointers) { + var pointers2 = assign({}, pointers); + var ratios = []; + forEach(pointers, function (pointer, pointerId) { + delete pointers2[pointerId]; + forEach(pointers2, function (pointer2) { + var x1 = Math.abs(pointer.startX - pointer2.startX); + var y1 = Math.abs(pointer.startY - pointer2.startY); + var x2 = Math.abs(pointer.endX - pointer2.endX); + var y2 = Math.abs(pointer.endY - pointer2.endY); + var z1 = Math.sqrt(x1 * x1 + y1 * y1); + var z2 = Math.sqrt(x2 * x2 + y2 * y2); + var ratio = (z2 - z1) / z1; + ratios.push(ratio); + }); + }); + ratios.sort(function (a, b) { + return Math.abs(a) < Math.abs(b); + }); + return ratios[0]; +} +/** + * Get a pointer from an event object. + * @param {Object} event - The target event object. + * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not. + * @returns {Object} The result pointer contains start and/or end point coordinates. + */ + +function getPointer(_ref2, endOnly) { + var pageX = _ref2.pageX, + pageY = _ref2.pageY; + var end = { + endX: pageX, + endY: pageY + }; + return endOnly ? end : assign({ + timeStamp: Date.now(), + startX: pageX, + startY: pageY + }, end); +} +/** + * Get the center point coordinate of a group of pointers. + * @param {Object} pointers - The target pointers. + * @returns {Object} The center point coordinate. + */ + +function getPointersCenter(pointers) { + var pageX = 0; + var pageY = 0; + var count = 0; + forEach(pointers, function (_ref3) { + var startX = _ref3.startX, + startY = _ref3.startY; + pageX += startX; + pageY += startY; + count += 1; + }); + pageX /= count; + pageY /= count; + return { + pageX: pageX, + pageY: pageY + }; +} + +var render = { + render: function render() { + this.initContainer(); + this.initViewer(); + this.initList(); + this.renderViewer(); + }, + initContainer: function initContainer() { + this.containerData = { + width: window.innerWidth, + height: window.innerHeight + }; + }, + initViewer: function initViewer() { + var options = this.options, + parent = this.parent; + var viewerData; + + if (options.inline) { + viewerData = { + width: Math.max(parent.offsetWidth, options.minWidth), + height: Math.max(parent.offsetHeight, options.minHeight) + }; + this.parentData = viewerData; + } + + if (this.fulled || !viewerData) { + viewerData = this.containerData; + } + + this.viewerData = assign({}, viewerData); + }, + renderViewer: function renderViewer() { + if (this.options.inline && !this.fulled) { + setStyle(this.viewer, this.viewerData); + } + }, + initList: function initList() { + var _this = this; + + var element = this.element, + options = this.options, + list = this.list; + var items = []; + forEach(this.images, function (image, i) { + var src = image.src; + var alt = image.alt || getImageNameFromURL(src); + var url = options.url; + + if (isString(url)) { + url = image.getAttribute(url); + } else if (isFunction(url)) { + url = url.call(_this, image); + } + + if (src || url) { + items.push('
    • ' + '' + '
    • '); + } + }); + list.innerHTML = items.join(''); + this.items = list.getElementsByTagName('li'); + forEach(this.items, function (item) { + var image = item.firstElementChild; + setData(image, 'filled', true); + + if (options.loading) { + addClass(item, CLASS_LOADING); + } + + addListener(image, EVENT_LOAD, function (event) { + if (options.loading) { + removeClass(item, CLASS_LOADING); + } + + _this.loadImage(event); + }, { + once: true + }); + }); + + if (options.transition) { + addListener(element, EVENT_VIEWED, function () { + addClass(list, CLASS_TRANSITION); + }, { + once: true + }); + } + }, + renderList: function renderList(index) { + var i = index || this.index; + var width = this.items[i].offsetWidth || 30; + var outerWidth = width + 1; // 1 pixel of `margin-left` width + // Place the active item in the center of the screen + + setStyle(this.list, assign({ + width: outerWidth * this.length + }, getTransforms({ + translateX: (this.viewerData.width - width) / 2 - outerWidth * i + }))); + }, + resetList: function resetList() { + var list = this.list; + list.innerHTML = ''; + removeClass(list, CLASS_TRANSITION); + setStyle(list, getTransforms({ + translateX: 0 + })); + }, + initImage: function initImage(done) { + var _this2 = this; + + var options = this.options, + image = this.image, + viewerData = this.viewerData; + var footerHeight = this.footer.offsetHeight; + var viewerWidth = viewerData.width; + var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight); + var oldImageData = this.imageData || {}; + var sizingImage; + this.imageInitializing = { + abort: function abort() { + sizingImage.onload = null; + } + }; + sizingImage = getImageNaturalSizes(image, function (naturalWidth, naturalHeight) { + var aspectRatio = naturalWidth / naturalHeight; + var width = viewerWidth; + var height = viewerHeight; + _this2.imageInitializing = false; + + if (viewerHeight * aspectRatio > viewerWidth) { + height = viewerWidth / aspectRatio; + } else { + width = viewerHeight * aspectRatio; + } + + width = Math.min(width * 0.9, naturalWidth); + height = Math.min(height * 0.9, naturalHeight); + var imageData = { + naturalWidth: naturalWidth, + naturalHeight: naturalHeight, + aspectRatio: aspectRatio, + ratio: width / naturalWidth, + width: width, + height: height, + left: (viewerWidth - width) / 2, + top: (viewerHeight - height) / 2 + }; + var initialImageData = assign({}, imageData); + + if (options.rotatable) { + imageData.rotate = oldImageData.rotate || 0; + initialImageData.rotate = 0; + } + + if (options.scalable) { + imageData.scaleX = oldImageData.scaleX || 1; + imageData.scaleY = oldImageData.scaleY || 1; + initialImageData.scaleX = 1; + initialImageData.scaleY = 1; + } + + _this2.imageData = imageData; + _this2.initialImageData = initialImageData; + + if (done) { + done(); + } + }); + }, + renderImage: function renderImage(done) { + var _this3 = this; + + var image = this.image, + imageData = this.imageData; + setStyle(image, assign({ + width: imageData.width, + height: imageData.height, + // XXX: Not to use translateX/Y to avoid image shaking when zooming + marginLeft: imageData.left, + marginTop: imageData.top + }, getTransforms(imageData))); + + if (done) { + if ((this.viewing || this.zooming) && this.options.transition) { + var onTransitionEnd = function onTransitionEnd() { + _this3.imageRendering = false; + done(); + }; + + this.imageRendering = { + abort: function abort() { + removeListener(image, EVENT_TRANSITION_END, onTransitionEnd); + } + }; + addListener(image, EVENT_TRANSITION_END, onTransitionEnd, { + once: true + }); + } else { + done(); + } + } + }, + resetImage: function resetImage() { + // this.image only defined after viewed + if (this.viewing || this.viewed) { + var image = this.image; + + if (this.viewing) { + this.viewing.abort(); + } + + image.parentNode.removeChild(image); + this.image = null; + } + } +}; + +var events = { + bind: function bind() { + var options = this.options, + viewer = this.viewer, + canvas = this.canvas; + var document = this.element.ownerDocument; + addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this)); + addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), { + passive: false, + capture: true + }); + addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this)); + addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this)); + addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this)); + addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this)); + addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this)); + addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this)); + + if (options.toggleOnDblclick) { + addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this)); + } + }, + unbind: function unbind() { + var options = this.options, + viewer = this.viewer, + canvas = this.canvas; + var document = this.element.ownerDocument; + removeListener(viewer, EVENT_CLICK, this.onClick); + removeListener(viewer, EVENT_WHEEL, this.onWheel, { + passive: false, + capture: true + }); + removeListener(viewer, EVENT_DRAG_START, this.onDragStart); + removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown); + removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove); + removeListener(document, EVENT_POINTER_UP, this.onPointerUp); + removeListener(document, EVENT_KEY_DOWN, this.onKeyDown); + removeListener(window, EVENT_RESIZE, this.onResize); + + if (options.toggleOnDblclick) { + removeListener(canvas, EVENT_DBLCLICK, this.onDblclick); + } + } +}; + +var handlers = { + click: function click(event) { + var target = event.target; + var options = this.options, + imageData = this.imageData; + var action = getData(target, DATA_ACTION); // Cancel the emulated click when the native click event was triggered. + + if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) { + clearTimeout(this.clickCanvasTimeout); + } + + switch (action) { + case 'mix': + if (this.played) { + this.stop(); + } else if (options.inline) { + if (this.fulled) { + this.exit(); + } else { + this.full(); + } + } else { + this.hide(); + } + + break; + + case 'hide': + this.hide(); + break; + + case 'view': + this.view(getData(target, 'index')); + break; + + case 'zoom-in': + this.zoom(0.1, true); + break; + + case 'zoom-out': + this.zoom(-0.1, true); + break; + + case 'one-to-one': + this.toggle(); + break; + + case 'reset': + this.reset(); + break; + + case 'prev': + this.prev(options.loop); + break; + + case 'play': + this.play(options.fullscreen); + break; + + case 'next': + this.next(options.loop); + break; + + case 'rotate-left': + this.rotate(-90); + break; + + case 'rotate-right': + this.rotate(90); + break; + + case 'flip-horizontal': + this.scaleX(-imageData.scaleX || -1); + break; + + case 'flip-vertical': + this.scaleY(-imageData.scaleY || -1); + break; + + default: + if (this.played) { + this.stop(); + } + + } + }, + dblclick: function dblclick(event) { + event.preventDefault(); + + if (this.viewed && event.target === this.image) { + // Cancel the emulated double click when the native dblclick event was triggered. + if (IS_TOUCH_DEVICE && event.isTrusted) { + clearTimeout(this.doubleClickImageTimeout); + } + + this.toggle(); + } + }, + load: function load() { + var _this = this; + + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = false; + } + + var element = this.element, + options = this.options, + image = this.image, + index = this.index, + viewerData = this.viewerData; + removeClass(image, CLASS_INVISIBLE); + + if (options.loading) { + removeClass(this.canvas, CLASS_LOADING); + } + + image.style.cssText = 'height:0;' + "margin-left:".concat(viewerData.width / 2, "px;") + "margin-top:".concat(viewerData.height / 2, "px;") + 'max-width:none!important;' + 'position:absolute;' + 'width:0;'; + this.initImage(function () { + toggleClass(image, CLASS_MOVE, options.movable); + toggleClass(image, CLASS_TRANSITION, options.transition); + + _this.renderImage(function () { + _this.viewed = true; + _this.viewing = false; + + if (isFunction(options.viewed)) { + addListener(element, EVENT_VIEWED, options.viewed, { + once: true + }); + } + + dispatchEvent(element, EVENT_VIEWED, { + originalImage: _this.images[index], + index: index, + image: image + }); + }); + }); + }, + loadImage: function loadImage(event) { + var image = event.target; + var parent = image.parentNode; + var parentWidth = parent.offsetWidth || 30; + var parentHeight = parent.offsetHeight || 50; + var filled = !!getData(image, 'filled'); + getImageNaturalSizes(image, function (naturalWidth, naturalHeight) { + var aspectRatio = naturalWidth / naturalHeight; + var width = parentWidth; + var height = parentHeight; + + if (parentHeight * aspectRatio > parentWidth) { + if (filled) { + width = parentHeight * aspectRatio; + } else { + height = parentWidth / aspectRatio; + } + } else if (filled) { + height = parentWidth / aspectRatio; + } else { + width = parentHeight * aspectRatio; + } + + setStyle(image, assign({ + width: width, + height: height + }, getTransforms({ + translateX: (parentWidth - width) / 2, + translateY: (parentHeight - height) / 2 + }))); + }); + }, + keydown: function keydown(event) { + var options = this.options; + + if (!this.fulled || !options.keyboard) { + return; + } + + switch (event.keyCode || event.which || event.charCode) { + // Escape + case 27: + if (this.played) { + this.stop(); + } else if (options.inline) { + if (this.fulled) { + this.exit(); + } + } else { + this.hide(); + } + + break; + // Space + + case 32: + if (this.played) { + this.stop(); + } + + break; + // ArrowLeft + + case 37: + this.prev(options.loop); + break; + // ArrowUp + + case 38: + // Prevent scroll on Firefox + event.preventDefault(); // Zoom in + + this.zoom(options.zoomRatio, true); + break; + // ArrowRight + + case 39: + this.next(options.loop); + break; + // ArrowDown + + case 40: + // Prevent scroll on Firefox + event.preventDefault(); // Zoom out + + this.zoom(-options.zoomRatio, true); + break; + // Ctrl + 0 + + case 48: // Fall through + // Ctrl + 1 + // eslint-disable-next-line no-fallthrough + + case 49: + if (event.ctrlKey) { + event.preventDefault(); + this.toggle(); + } + + break; + + default: + } + }, + dragstart: function dragstart(event) { + if (event.target.tagName.toLowerCase() === 'img') { + event.preventDefault(); + } + }, + pointerdown: function pointerdown(event) { + var options = this.options, + pointers = this.pointers; + var buttons = event.buttons, + button = event.button; + + if (!this.viewed || this.showing || this.viewing || this.hiding // No primary button (Usually the left button) + // Note that touch events have no `buttons` or `button` property + || isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0 // Open context menu + || event.ctrlKey) { + return; + } // Prevent default behaviours as page zooming in touch devices. + + + event.preventDefault(); + + if (event.changedTouches) { + forEach(event.changedTouches, function (touch) { + pointers[touch.identifier] = getPointer(touch); + }); + } else { + pointers[event.pointerId || 0] = getPointer(event); + } + + var action = options.movable ? ACTION_MOVE : false; + + if (Object.keys(pointers).length > 1) { + action = ACTION_ZOOM; + } else if ((event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) { + action = ACTION_SWITCH; + } + + if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) { + removeClass(this.image, CLASS_TRANSITION); + } + + this.action = action; + }, + pointermove: function pointermove(event) { + var pointers = this.pointers, + action = this.action; + + if (!this.viewed || !action) { + return; + } + + event.preventDefault(); + + if (event.changedTouches) { + forEach(event.changedTouches, function (touch) { + assign(pointers[touch.identifier] || {}, getPointer(touch, true)); + }); + } else { + assign(pointers[event.pointerId || 0] || {}, getPointer(event, true)); + } + + this.change(event); + }, + pointerup: function pointerup(event) { + var _this2 = this; + + var options = this.options, + action = this.action, + pointers = this.pointers; + var pointer; + + if (event.changedTouches) { + forEach(event.changedTouches, function (touch) { + pointer = pointers[touch.identifier]; + delete pointers[touch.identifier]; + }); + } else { + pointer = pointers[event.pointerId || 0]; + delete pointers[event.pointerId || 0]; + } + + if (!action) { + return; + } + + event.preventDefault(); + + if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) { + addClass(this.image, CLASS_TRANSITION); + } + + this.action = false; // Emulate click and double click in touch devices to support backdrop and image zooming (#210). + + if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) { + clearTimeout(this.clickCanvasTimeout); + clearTimeout(this.doubleClickImageTimeout); + + if (options.toggleOnDblclick && this.viewed && event.target === this.image) { + if (this.imageClicked) { + this.imageClicked = false; // This timeout will be cleared later when a native dblclick event is triggering + + this.doubleClickImageTimeout = setTimeout(function () { + dispatchEvent(_this2.image, EVENT_DBLCLICK); + }, 50); + } else { + this.imageClicked = true; // The default timing of a double click in Windows is 500 ms + + this.doubleClickImageTimeout = setTimeout(function () { + _this2.imageClicked = false; + }, 500); + } + } else { + this.imageClicked = false; + + if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) { + // This timeout will be cleared later when a native click event is triggering + this.clickCanvasTimeout = setTimeout(function () { + dispatchEvent(_this2.canvas, EVENT_CLICK); + }, 50); + } + } + } + }, + resize: function resize() { + var _this3 = this; + + if (!this.isShown || this.hiding) { + return; + } + + this.initContainer(); + this.initViewer(); + this.renderViewer(); + this.renderList(); + + if (this.viewed) { + this.initImage(function () { + _this3.renderImage(); + }); + } + + if (this.played) { + if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { + this.stop(); + return; + } + + forEach(this.player.getElementsByTagName('img'), function (image) { + addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), { + once: true + }); + dispatchEvent(image, EVENT_LOAD); + }); + } + }, + wheel: function wheel(event) { + var _this4 = this; + + if (!this.viewed) { + return; + } + + event.preventDefault(); // Limit wheel speed to prevent zoom too fast + + if (this.wheeling) { + return; + } + + this.wheeling = true; + setTimeout(function () { + _this4.wheeling = false; + }, 50); + var ratio = Number(this.options.zoomRatio) || 0.1; + var delta = 1; + + if (event.deltaY) { + delta = event.deltaY > 0 ? 1 : -1; + } else if (event.wheelDelta) { + delta = -event.wheelDelta / 120; + } else if (event.detail) { + delta = event.detail > 0 ? 1 : -1; + } + + this.zoom(-delta * ratio, true, event); + } +}; + +var methods = { + /** Show the viewer (only available in modal mode) + * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not. + * @returns {Viewer} this + */ + show: function show() { + var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var element = this.element, + options = this.options; + + if (options.inline || this.showing || this.isShown || this.showing) { + return this; + } + + if (!this.ready) { + this.build(); + + if (this.ready) { + this.show(immediate); + } + + return this; + } + + if (isFunction(options.show)) { + addListener(element, EVENT_SHOW, options.show, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) { + return this; + } + + if (this.hiding) { + this.transitioning.abort(); + } + + this.showing = true; + this.open(); + var viewer = this.viewer; + removeClass(viewer, CLASS_HIDE); + + if (options.transition && !immediate) { + var shown = this.shown.bind(this); + this.transitioning = { + abort: function abort() { + removeListener(viewer, EVENT_TRANSITION_END, shown); + removeClass(viewer, CLASS_IN); + } + }; + addClass(viewer, CLASS_TRANSITION); // Force reflow to enable CSS3 transition + // eslint-disable-next-line + + viewer.offsetWidth; + addListener(viewer, EVENT_TRANSITION_END, shown, { + once: true + }); + addClass(viewer, CLASS_IN); + } else { + addClass(viewer, CLASS_IN); + this.shown(); + } + + return this; + }, + + /** + * Hide the viewer (only available in modal mode) + * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not. + * @returns {Viewer} this + */ + hide: function hide() { + var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var element = this.element, + options = this.options; + + if (options.inline || this.hiding || !(this.isShown || this.showing)) { + return this; + } + + if (isFunction(options.hide)) { + addListener(element, EVENT_HIDE, options.hide, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_HIDE) === false) { + return this; + } + + if (this.showing) { + this.transitioning.abort(); + } + + this.hiding = true; + + if (this.played) { + this.stop(); + } else if (this.viewing) { + this.viewing.abort(); + } + + var viewer = this.viewer; + + if (options.transition && !immediate) { + var hidden = this.hidden.bind(this); + + var hide = function hide() { + addListener(viewer, EVENT_TRANSITION_END, hidden, { + once: true + }); + removeClass(viewer, CLASS_IN); + }; + + this.transitioning = { + abort: function abort() { + if (this.viewed) { + removeListener(this.image, EVENT_TRANSITION_END, hide); + } else { + removeListener(viewer, EVENT_TRANSITION_END, hidden); + } + } + }; // Note that the `CLASS_TRANSITION` class will be removed on pointer down (#255) + + if (this.viewed && hasClass(this.image, CLASS_TRANSITION)) { + addListener(this.image, EVENT_TRANSITION_END, hide, { + once: true + }); + this.zoomTo(0, false, false, true); + } else { + hide(); + } + } else { + removeClass(viewer, CLASS_IN); + this.hidden(); + } + + return this; + }, + + /** + * View one of the images with image's index + * @param {number} index - The index of the image to view. + * @returns {Viewer} this + */ + view: function view() { + var _this = this; + + var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex; + index = Number(index) || 0; + + if (!this.isShown) { + this.index = index; + return this.show(); + } + + if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) { + return this; + } + + if (this.viewing) { + this.viewing.abort(); + } + + var element = this.element, + options = this.options, + title = this.title, + canvas = this.canvas; + var item = this.items[index]; + var img = item.querySelector('img'); + var url = getData(img, 'originalUrl'); + var alt = img.getAttribute('alt'); + var image = document.createElement('img'); + image.src = url; + image.alt = alt; + + if (isFunction(options.view)) { + addListener(element, EVENT_VIEW, options.view, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_VIEW, { + originalImage: this.images[index], + index: index, + image: image + }) === false || !this.isShown || this.hiding || this.played) { + return this; + } + + this.image = image; + removeClass(this.items[this.index], CLASS_ACTIVE); + addClass(item, CLASS_ACTIVE); + this.viewed = false; + this.index = index; + this.imageData = {}; + addClass(image, CLASS_INVISIBLE); + + if (options.loading) { + addClass(canvas, CLASS_LOADING); + } + + canvas.innerHTML = ''; + canvas.appendChild(image); // Center current item + + this.renderList(); // Clear title + + title.innerHTML = ''; // Generate title after viewed + + var onViewed = function onViewed() { + var imageData = _this.imageData; + var render = Array.isArray(options.title) ? options.title[1] : options.title; + title.innerHTML = isFunction(render) ? render.call(_this, image, imageData) : "".concat(alt, " (").concat(imageData.naturalWidth, " \xD7 ").concat(imageData.naturalHeight, ")"); + }; + + var onLoad; + addListener(element, EVENT_VIEWED, onViewed, { + once: true + }); + this.viewing = { + abort: function abort() { + removeListener(element, EVENT_VIEWED, onViewed); + + if (image.complete) { + if (this.imageRendering) { + this.imageRendering.abort(); + } else if (this.imageInitializing) { + this.imageInitializing.abort(); + } + } else { + // Cancel download to save bandwidth. + image.src = ''; + removeListener(image, EVENT_LOAD, onLoad); + + if (this.timeout) { + clearTimeout(this.timeout); + } + } + } + }; + + if (image.complete) { + this.load(); + } else { + addListener(image, EVENT_LOAD, onLoad = this.load.bind(this), { + once: true + }); + + if (this.timeout) { + clearTimeout(this.timeout); + } // Make the image visible if it fails to load within 1s + + + this.timeout = setTimeout(function () { + removeClass(image, CLASS_INVISIBLE); + _this.timeout = false; + }, 1000); + } + + return this; + }, + + /** + * View the previous image + * @param {boolean} [loop=false] - Indicate if view the last one + * when it is the first one at present. + * @returns {Viewer} this + */ + prev: function prev() { + var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var index = this.index - 1; + + if (index < 0) { + index = loop ? this.length - 1 : 0; + } + + this.view(index); + return this; + }, + + /** + * View the next image + * @param {boolean} [loop=false] - Indicate if view the first one + * when it is the last one at present. + * @returns {Viewer} this + */ + next: function next() { + var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var maxIndex = this.length - 1; + var index = this.index + 1; + + if (index > maxIndex) { + index = loop ? 0 : maxIndex; + } + + this.view(index); + return this; + }, + + /** + * Move the image with relative offsets. + * @param {number} offsetX - The relative offset distance on the x-axis. + * @param {number} offsetY - The relative offset distance on the y-axis. + * @returns {Viewer} this + */ + move: function move(offsetX, offsetY) { + var imageData = this.imageData; + this.moveTo(isUndefined(offsetX) ? offsetX : imageData.left + Number(offsetX), isUndefined(offsetY) ? offsetY : imageData.top + Number(offsetY)); + return this; + }, + + /** + * Move the image to an absolute point. + * @param {number} x - The x-axis coordinate. + * @param {number} [y=x] - The y-axis coordinate. + * @returns {Viewer} this + */ + moveTo: function moveTo(x) { + var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x; + var imageData = this.imageData; + x = Number(x); + y = Number(y); + + if (this.viewed && !this.played && this.options.movable) { + var changed = false; + + if (isNumber(x)) { + imageData.left = x; + changed = true; + } + + if (isNumber(y)) { + imageData.top = y; + changed = true; + } + + if (changed) { + this.renderImage(); + } + } + + return this; + }, + + /** + * Zoom the image with a relative ratio. + * @param {number} ratio - The target ratio. + * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not. + * @param {Event} [_originalEvent=null] - The original event if any. + * @returns {Viewer} this + */ + zoom: function zoom(ratio) { + var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + + var imageData = this.imageData; + ratio = Number(ratio); + + if (ratio < 0) { + ratio = 1 / (1 - ratio); + } else { + ratio = 1 + ratio; + } + + this.zoomTo(imageData.width * ratio / imageData.naturalWidth, hasTooltip, _originalEvent); + return this; + }, + + /** + * Zoom the image to an absolute ratio. + * @param {number} ratio - The target ratio. + * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not. + * @param {Event} [_originalEvent=null] - The original event if any. + * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not. + * @returns {Viewer} this + */ + zoomTo: function zoomTo(ratio) { + var _this2 = this; + + var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + + var _zoomable = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + + var element = this.element, + options = this.options, + pointers = this.pointers, + imageData = this.imageData; + var width = imageData.width, + height = imageData.height, + left = imageData.left, + top = imageData.top, + naturalWidth = imageData.naturalWidth, + naturalHeight = imageData.naturalHeight; + ratio = Math.max(0, ratio); + + if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) { + if (!_zoomable) { + var minZoomRatio = Math.max(0.01, options.minZoomRatio); + var maxZoomRatio = Math.min(100, options.maxZoomRatio); + ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio); + } + + if (_originalEvent && ratio > 0.95 && ratio < 1.05) { + ratio = 1; + } + + var newWidth = naturalWidth * ratio; + var newHeight = naturalHeight * ratio; + var offsetWidth = newWidth - width; + var offsetHeight = newHeight - height; + var oldRatio = width / naturalWidth; + + if (isFunction(options.zoom)) { + addListener(element, EVENT_ZOOM, options.zoom, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_ZOOM, { + ratio: ratio, + oldRatio: oldRatio, + originalEvent: _originalEvent + }) === false) { + return this; + } + + this.zooming = true; + + if (_originalEvent) { + var offset = getOffset(this.viewer); + var center = pointers && Object.keys(pointers).length ? getPointersCenter(pointers) : { + pageX: _originalEvent.pageX, + pageY: _originalEvent.pageY + }; // Zoom from the triggering point of the event + + imageData.left -= offsetWidth * ((center.pageX - offset.left - left) / width); + imageData.top -= offsetHeight * ((center.pageY - offset.top - top) / height); + } else { + // Zoom from the center of the image + imageData.left -= offsetWidth / 2; + imageData.top -= offsetHeight / 2; + } + + imageData.width = newWidth; + imageData.height = newHeight; + imageData.ratio = ratio; + this.renderImage(function () { + _this2.zooming = false; + + if (isFunction(options.zoomed)) { + addListener(element, EVENT_ZOOMED, options.zoomed, { + once: true + }); + } + + dispatchEvent(element, EVENT_ZOOMED, { + ratio: ratio, + oldRatio: oldRatio, + originalEvent: _originalEvent + }); + }); + + if (hasTooltip) { + this.tooltip(); + } + } + + return this; + }, + + /** + * Rotate the image with a relative degree. + * @param {number} degree - The rotate degree. + * @returns {Viewer} this + */ + rotate: function rotate(degree) { + this.rotateTo((this.imageData.rotate || 0) + Number(degree)); + return this; + }, + + /** + * Rotate the image to an absolute degree. + * @param {number} degree - The rotate degree. + * @returns {Viewer} this + */ + rotateTo: function rotateTo(degree) { + var imageData = this.imageData; + degree = Number(degree); + + if (isNumber(degree) && this.viewed && !this.played && this.options.rotatable) { + imageData.rotate = degree; + this.renderImage(); + } + + return this; + }, + + /** + * Scale the image on the x-axis. + * @param {number} scaleX - The scale ratio on the x-axis. + * @returns {Viewer} this + */ + scaleX: function scaleX(_scaleX) { + this.scale(_scaleX, this.imageData.scaleY); + return this; + }, + + /** + * Scale the image on the y-axis. + * @param {number} scaleY - The scale ratio on the y-axis. + * @returns {Viewer} this + */ + scaleY: function scaleY(_scaleY) { + this.scale(this.imageData.scaleX, _scaleY); + return this; + }, + + /** + * Scale the image. + * @param {number} scaleX - The scale ratio on the x-axis. + * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis. + * @returns {Viewer} this + */ + scale: function scale(scaleX) { + var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX; + var imageData = this.imageData; + scaleX = Number(scaleX); + scaleY = Number(scaleY); + + if (this.viewed && !this.played && this.options.scalable) { + var changed = false; + + if (isNumber(scaleX)) { + imageData.scaleX = scaleX; + changed = true; + } + + if (isNumber(scaleY)) { + imageData.scaleY = scaleY; + changed = true; + } + + if (changed) { + this.renderImage(); + } + } + + return this; + }, + + /** + * Play the images + * @param {boolean} [fullscreen=false] - Indicate if request fullscreen or not. + * @returns {Viewer} this + */ + play: function play() { + var _this3 = this; + + var fullscreen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + + if (!this.isShown || this.played) { + return this; + } + + var options = this.options, + player = this.player; + var onLoad = this.loadImage.bind(this); + var list = []; + var total = 0; + var index = 0; + this.played = true; + this.onLoadWhenPlay = onLoad; + + if (fullscreen) { + this.requestFullscreen(); + } + + addClass(player, CLASS_SHOW); + forEach(this.items, function (item, i) { + var img = item.querySelector('img'); + var image = document.createElement('img'); + image.src = getData(img, 'originalUrl'); + image.alt = img.getAttribute('alt'); + total += 1; + addClass(image, CLASS_FADE); + toggleClass(image, CLASS_TRANSITION, options.transition); + + if (hasClass(item, CLASS_ACTIVE)) { + addClass(image, CLASS_IN); + index = i; + } + + list.push(image); + addListener(image, EVENT_LOAD, onLoad, { + once: true + }); + player.appendChild(image); + }); + + if (isNumber(options.interval) && options.interval > 0) { + var play = function play() { + _this3.playing = setTimeout(function () { + removeClass(list[index], CLASS_IN); + index += 1; + index = index < total ? index : 0; + addClass(list[index], CLASS_IN); + play(); + }, options.interval); + }; + + if (total > 1) { + play(); + } + } + + return this; + }, + // Stop play + stop: function stop() { + var _this4 = this; + + if (!this.played) { + return this; + } + + var player = this.player; + this.played = false; + clearTimeout(this.playing); + forEach(player.getElementsByTagName('img'), function (image) { + removeListener(image, EVENT_LOAD, _this4.onLoadWhenPlay); + }); + removeClass(player, CLASS_SHOW); + player.innerHTML = ''; + this.exitFullscreen(); + return this; + }, + // Enter modal mode (only available in inline mode) + full: function full() { + var _this5 = this; + + var options = this.options, + viewer = this.viewer, + image = this.image, + list = this.list; + + if (!this.isShown || this.played || this.fulled || !options.inline) { + return this; + } + + this.fulled = true; + this.open(); + addClass(this.button, CLASS_FULLSCREEN_EXIT); + + if (options.transition) { + removeClass(list, CLASS_TRANSITION); + + if (this.viewed) { + removeClass(image, CLASS_TRANSITION); + } + } + + addClass(viewer, CLASS_FIXED); + viewer.setAttribute('style', ''); + setStyle(viewer, { + zIndex: options.zIndex + }); + this.initContainer(); + this.viewerData = assign({}, this.containerData); + this.renderList(); + + if (this.viewed) { + this.initImage(function () { + _this5.renderImage(function () { + if (options.transition) { + setTimeout(function () { + addClass(image, CLASS_TRANSITION); + addClass(list, CLASS_TRANSITION); + }, 0); + } + }); + }); + } + + return this; + }, + // Exit modal mode (only available in inline mode) + exit: function exit() { + var _this6 = this; + + var options = this.options, + viewer = this.viewer, + image = this.image, + list = this.list; + + if (!this.isShown || this.played || !this.fulled || !options.inline) { + return this; + } + + this.fulled = false; + this.close(); + removeClass(this.button, CLASS_FULLSCREEN_EXIT); + + if (options.transition) { + removeClass(list, CLASS_TRANSITION); + + if (this.viewed) { + removeClass(image, CLASS_TRANSITION); + } + } + + removeClass(viewer, CLASS_FIXED); + setStyle(viewer, { + zIndex: options.zIndexInline + }); + this.viewerData = assign({}, this.parentData); + this.renderViewer(); + this.renderList(); + + if (this.viewed) { + this.initImage(function () { + _this6.renderImage(function () { + if (options.transition) { + setTimeout(function () { + addClass(image, CLASS_TRANSITION); + addClass(list, CLASS_TRANSITION); + }, 0); + } + }); + }); + } + + return this; + }, + // Show the current ratio of the image with percentage + tooltip: function tooltip() { + var _this7 = this; + + var options = this.options, + tooltipBox = this.tooltipBox, + imageData = this.imageData; + + if (!this.viewed || this.played || !options.tooltip) { + return this; + } + + tooltipBox.textContent = "".concat(Math.round(imageData.ratio * 100), "%"); + + if (!this.tooltipping) { + if (options.transition) { + if (this.fading) { + dispatchEvent(tooltipBox, EVENT_TRANSITION_END); + } + + addClass(tooltipBox, CLASS_SHOW); + addClass(tooltipBox, CLASS_FADE); + addClass(tooltipBox, CLASS_TRANSITION); // Force reflow to enable CSS3 transition + // eslint-disable-next-line + + tooltipBox.offsetWidth; + addClass(tooltipBox, CLASS_IN); + } else { + addClass(tooltipBox, CLASS_SHOW); + } + } else { + clearTimeout(this.tooltipping); + } + + this.tooltipping = setTimeout(function () { + if (options.transition) { + addListener(tooltipBox, EVENT_TRANSITION_END, function () { + removeClass(tooltipBox, CLASS_SHOW); + removeClass(tooltipBox, CLASS_FADE); + removeClass(tooltipBox, CLASS_TRANSITION); + _this7.fading = false; + }, { + once: true + }); + removeClass(tooltipBox, CLASS_IN); + _this7.fading = true; + } else { + removeClass(tooltipBox, CLASS_SHOW); + } + + _this7.tooltipping = false; + }, 1000); + return this; + }, + // Toggle the image size between its natural size and initial size + toggle: function toggle() { + if (this.imageData.ratio === 1) { + this.zoomTo(this.initialImageData.ratio, true); + } else { + this.zoomTo(1, true); + } + + return this; + }, + // Reset the image to its initial state + reset: function reset() { + if (this.viewed && !this.played) { + this.imageData = assign({}, this.initialImageData); + this.renderImage(); + } + + return this; + }, + // Update viewer when images changed + update: function update() { + var element = this.element, + options = this.options, + isImg = this.isImg; // Destroy viewer if the target image was deleted + + if (isImg && !element.parentNode) { + return this.destroy(); + } + + var images = []; + forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) { + if (options.filter) { + if (options.filter(image)) { + images.push(image); + } + } else { + images.push(image); + } + }); + + if (!images.length) { + return this; + } + + this.images = images; + this.length = images.length; + + if (this.ready) { + var indexes = []; + forEach(this.items, function (item, i) { + var img = item.querySelector('img'); + var image = images[i]; + + if (image) { + if (image.src !== img.src) { + indexes.push(i); + } + } else { + indexes.push(i); + } + }); + setStyle(this.list, { + width: 'auto' + }); + this.initList(); + + if (this.isShown) { + if (this.length) { + if (this.viewed) { + var index = indexes.indexOf(this.index); + + if (index >= 0) { + this.viewed = false; + this.view(Math.max(this.index - (index + 1), 0)); + } else { + addClass(this.items[this.index], CLASS_ACTIVE); + } + } + } else { + this.image = null; + this.viewed = false; + this.index = 0; + this.imageData = {}; + this.canvas.innerHTML = ''; + this.title.innerHTML = ''; + } + } + } else { + this.build(); + } + + return this; + }, + // Destroy the viewer + destroy: function destroy() { + var element = this.element, + options = this.options; + + if (!element[NAMESPACE]) { + return this; + } + + this.destroyed = true; + + if (this.ready) { + if (this.played) { + this.stop(); + } + + if (options.inline) { + if (this.fulled) { + this.exit(); + } + + this.unbind(); + } else if (this.isShown) { + if (this.viewing) { + if (this.imageRendering) { + this.imageRendering.abort(); + } else if (this.imageInitializing) { + this.imageInitializing.abort(); + } + } + + if (this.hiding) { + this.transitioning.abort(); + } + + this.hidden(); + } else if (this.showing) { + this.transitioning.abort(); + this.hidden(); + } + + this.ready = false; + this.viewer.parentNode.removeChild(this.viewer); + } else if (options.inline) { + if (this.delaying) { + this.delaying.abort(); + } else if (this.initializing) { + this.initializing.abort(); + } + } + + if (!options.inline) { + removeListener(element, EVENT_CLICK, this.onStart); + } + + element[NAMESPACE] = undefined; + return this; + } +}; + +var others = { + open: function open() { + var body = this.body; + addClass(body, CLASS_OPEN); + body.style.paddingRight = "".concat(this.scrollbarWidth + (parseFloat(this.initialBodyPaddingRight) || 0), "px"); + }, + close: function close() { + var body = this.body; + removeClass(body, CLASS_OPEN); + body.style.paddingRight = this.initialBodyPaddingRight; + }, + shown: function shown() { + var element = this.element, + options = this.options; + this.fulled = true; + this.isShown = true; + this.render(); + this.bind(); + this.showing = false; + + if (isFunction(options.shown)) { + addListener(element, EVENT_SHOWN, options.shown, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_SHOWN) === false) { + return; + } + + if (this.ready && this.isShown && !this.hiding) { + this.view(this.index); + } + }, + hidden: function hidden() { + var element = this.element, + options = this.options; + this.fulled = false; + this.viewed = false; + this.isShown = false; + this.close(); + this.unbind(); + addClass(this.viewer, CLASS_HIDE); + this.resetList(); + this.resetImage(); + this.hiding = false; + + if (!this.destroyed) { + if (isFunction(options.hidden)) { + addListener(element, EVENT_HIDDEN, options.hidden, { + once: true + }); + } + + dispatchEvent(element, EVENT_HIDDEN); + } + }, + requestFullscreen: function requestFullscreen() { + var document = this.element.ownerDocument; + + if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { + var documentElement = document.documentElement; // Element.requestFullscreen() + + if (documentElement.requestFullscreen) { + documentElement.requestFullscreen(); + } else if (documentElement.webkitRequestFullscreen) { + documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else if (documentElement.mozRequestFullScreen) { + documentElement.mozRequestFullScreen(); + } else if (documentElement.msRequestFullscreen) { + documentElement.msRequestFullscreen(); + } + } + }, + exitFullscreen: function exitFullscreen() { + var document = this.element.ownerDocument; + + if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { + // Document.exitFullscreen() + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } + } + }, + change: function change(event) { + var options = this.options, + pointers = this.pointers; + var pointer = pointers[Object.keys(pointers)[0]]; + var offsetX = pointer.endX - pointer.startX; + var offsetY = pointer.endY - pointer.startY; + + switch (this.action) { + // Move the current image + case ACTION_MOVE: + this.move(offsetX, offsetY); + break; + // Zoom the current image + + case ACTION_ZOOM: + this.zoom(getMaxZoomRatio(pointers), false, event); + break; + + case ACTION_SWITCH: + { + this.action = 'switched'; + var absoluteOffsetX = Math.abs(offsetX); + + if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) { + // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers. + this.pointers = {}; + + if (offsetX > 1) { + this.prev(options.loop); + } else if (offsetX < -1) { + this.next(options.loop); + } + } + + break; + } + + default: + } // Override + + + forEach(pointers, function (p) { + p.startX = p.endX; + p.startY = p.endY; + }); + }, + isSwitchable: function isSwitchable() { + var imageData = this.imageData, + viewerData = this.viewerData; + return this.length > 1 && imageData.left >= 0 && imageData.top >= 0 && imageData.width <= viewerData.width && imageData.height <= viewerData.height; + } +}; + +var AnotherViewer = WINDOW.Viewer; + +var Viewer = +/*#__PURE__*/ +function () { + /** + * Create a new Viewer. + * @param {Element} element - The target element for viewing. + * @param {Object} [options={}] - The configuration options. + */ + function Viewer(element) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, Viewer); + + if (!element || element.nodeType !== 1) { + throw new Error('The first argument is required and must be an element.'); + } + + this.element = element; + this.options = assign({}, DEFAULTS, isPlainObject(options) && options); + this.action = false; + this.fading = false; + this.fulled = false; + this.hiding = false; + this.imageClicked = false; + this.imageData = {}; + this.index = this.options.initialViewIndex; + this.isImg = false; + this.isShown = false; + this.length = 0; + this.played = false; + this.playing = false; + this.pointers = {}; + this.ready = false; + this.showing = false; + this.timeout = false; + this.tooltipping = false; + this.viewed = false; + this.viewing = false; + this.wheeling = false; + this.zooming = false; + this.init(); + } + + _createClass(Viewer, [{ + key: "init", + value: function init() { + var _this = this; + + var element = this.element, + options = this.options; + + if (element[NAMESPACE]) { + return; + } + + element[NAMESPACE] = this; + var isImg = element.tagName.toLowerCase() === 'img'; + var images = []; + forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) { + if (isFunction(options.filter)) { + if (options.filter.call(_this, image)) { + images.push(image); + } + } else { + images.push(image); + } + }); + this.isImg = isImg; + this.length = images.length; + this.images = images; + var ownerDocument = element.ownerDocument; + var body = ownerDocument.body || ownerDocument.documentElement; + this.body = body; + this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth; + this.initialBodyPaddingRight = window.getComputedStyle(body).paddingRight; // Override `transition` option if it is not supported + + if (isUndefined(document.createElement(NAMESPACE).style.transition)) { + options.transition = false; + } + + if (options.inline) { + var count = 0; + + var progress = function progress() { + count += 1; + + if (count === _this.length) { + var timeout; + _this.initializing = false; + _this.delaying = { + abort: function abort() { + clearTimeout(timeout); + } + }; // build asynchronously to keep `this.viewer` is accessible in `ready` event handler. + + timeout = setTimeout(function () { + _this.delaying = false; + + _this.build(); + }, 0); + } + }; + + this.initializing = { + abort: function abort() { + forEach(images, function (image) { + if (!image.complete) { + removeListener(image, EVENT_LOAD, progress); + } + }); + } + }; + forEach(images, function (image) { + if (image.complete) { + progress(); + } else { + addListener(image, EVENT_LOAD, progress, { + once: true + }); + } + }); + } else { + addListener(element, EVENT_CLICK, this.onStart = function (_ref) { + var target = _ref.target; + + if (target.tagName.toLowerCase() === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) { + _this.view(_this.images.indexOf(target)); + } + }); + } + } + }, { + key: "build", + value: function build() { + if (this.ready) { + return; + } + + var element = this.element, + options = this.options; + var parent = element.parentNode; + var template = document.createElement('div'); + template.innerHTML = TEMPLATE; + var viewer = template.querySelector(".".concat(NAMESPACE, "-container")); + var title = viewer.querySelector(".".concat(NAMESPACE, "-title")); + var toolbar = viewer.querySelector(".".concat(NAMESPACE, "-toolbar")); + var navbar = viewer.querySelector(".".concat(NAMESPACE, "-navbar")); + var button = viewer.querySelector(".".concat(NAMESPACE, "-button")); + var canvas = viewer.querySelector(".".concat(NAMESPACE, "-canvas")); + this.parent = parent; + this.viewer = viewer; + this.title = title; + this.toolbar = toolbar; + this.navbar = navbar; + this.button = button; + this.canvas = canvas; + this.footer = viewer.querySelector(".".concat(NAMESPACE, "-footer")); + this.tooltipBox = viewer.querySelector(".".concat(NAMESPACE, "-tooltip")); + this.player = viewer.querySelector(".".concat(NAMESPACE, "-player")); + this.list = viewer.querySelector(".".concat(NAMESPACE, "-list")); + addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title)); + addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar)); + toggleClass(button, CLASS_HIDE, !options.button); + + if (options.backdrop) { + addClass(viewer, "".concat(NAMESPACE, "-backdrop")); + + if (!options.inline && options.backdrop !== 'static') { + setData(canvas, DATA_ACTION, 'hide'); + } + } + + if (isString(options.className) && options.className) { + // In case there are multiple class names + options.className.split(REGEXP_SPACES).forEach(function (className) { + addClass(viewer, className); + }); + } + + if (options.toolbar) { + var list = document.createElement('ul'); + var custom = isPlainObject(options.toolbar); + var zoomButtons = BUTTONS.slice(0, 3); + var rotateButtons = BUTTONS.slice(7, 9); + var scaleButtons = BUTTONS.slice(9); + + if (!custom) { + addClass(toolbar, getResponsiveClass(options.toolbar)); + } + + forEach(custom ? options.toolbar : BUTTONS, function (value, index) { + var deep = custom && isPlainObject(value); + var name = custom ? hyphenate(index) : value; + var show = deep && !isUndefined(value.show) ? value.show : value; + + if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) { + return; + } + + var size = deep && !isUndefined(value.size) ? value.size : value; + var click = deep && !isUndefined(value.click) ? value.click : value; + var item = document.createElement('li'); + item.setAttribute('role', 'button'); + addClass(item, "".concat(NAMESPACE, "-").concat(name)); + + if (!isFunction(click)) { + setData(item, DATA_ACTION, name); + } + + if (isNumber(show)) { + addClass(item, getResponsiveClass(show)); + } + + if (['small', 'large'].indexOf(size) !== -1) { + addClass(item, "".concat(NAMESPACE, "-").concat(size)); + } else if (name === 'play') { + addClass(item, "".concat(NAMESPACE, "-large")); + } + + if (isFunction(click)) { + addListener(item, EVENT_CLICK, click); + } + + list.appendChild(item); + }); + toolbar.appendChild(list); + } else { + addClass(toolbar, CLASS_HIDE); + } + + if (!options.rotatable) { + var rotates = toolbar.querySelectorAll('li[class*="rotate"]'); + addClass(rotates, CLASS_INVISIBLE); + forEach(rotates, function (rotate) { + toolbar.appendChild(rotate); + }); + } + + if (options.inline) { + addClass(button, CLASS_FULLSCREEN); + setStyle(viewer, { + zIndex: options.zIndexInline + }); + + if (window.getComputedStyle(parent).position === 'static') { + setStyle(parent, { + position: 'relative' + }); + } + + parent.insertBefore(viewer, element.nextSibling); + } else { + addClass(button, CLASS_CLOSE); + addClass(viewer, CLASS_FIXED); + addClass(viewer, CLASS_FADE); + addClass(viewer, CLASS_HIDE); + setStyle(viewer, { + zIndex: options.zIndex + }); + var container = options.container; + + if (isString(container)) { + container = element.ownerDocument.querySelector(container); + } + + if (!container) { + container = this.body; + } + + container.appendChild(viewer); + } + + if (options.inline) { + this.render(); + this.bind(); + this.isShown = true; + } + + this.ready = true; + + if (isFunction(options.ready)) { + addListener(element, EVENT_READY, options.ready, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_READY) === false) { + this.ready = false; + return; + } + + if (this.ready && options.inline) { + this.view(this.index); + } + } + /** + * Get the no conflict viewer class. + * @returns {Viewer} The viewer class. + */ + + }], [{ + key: "noConflict", + value: function noConflict() { + window.Viewer = AnotherViewer; + return Viewer; + } + /** + * Change the default options. + * @param {Object} options - The new default options. + */ + + }, { + key: "setDefaults", + value: function setDefaults(options) { + assign(DEFAULTS, isPlainObject(options) && options); + } + }]); + + return Viewer; +}(); + +assign(Viewer.prototype, render, events, handlers, methods, others); + +module.exports = Viewer; diff --git a/src/main/resources/templates/graduation/dist/viewer/js/viewer.esm.js b/src/main/resources/templates/graduation/dist/viewer/js/viewer.esm.js new file mode 100644 index 0000000000000000000000000000000000000000..7a4df656c19d6dc22b4db86a9c4121761c80b095 --- /dev/null +++ b/src/main/resources/templates/graduation/dist/viewer/js/viewer.esm.js @@ -0,0 +1,3014 @@ +/*! + * Viewer.js v1.3.3 + * https://fengyuanchen.github.io/viewerjs + * + * Copyright 2015-present Chen Fengyuan + * Released under the MIT license + * + * Date: 2019-04-06T14:06:28.301Z + */ + +function _typeof(obj) { + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function (obj) { + return typeof obj; + }; + } else { + _typeof = function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + } + + return _typeof(obj); +} + +function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +} + +function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } +} + +function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; +} + +var DEFAULTS = { + /** + * Enable a modal backdrop, specify `static` for a backdrop + * which doesn't close the modal on click. + * @type {boolean} + */ + backdrop: true, + + /** + * Show the button on the top-right of the viewer. + * @type {boolean} + */ + button: true, + + /** + * Show the navbar. + * @type {boolean | number} + */ + navbar: true, + + /** + * Specify the visibility and the content of the title. + * @type {boolean | number | Function | Array} + */ + title: true, + + /** + * Show the toolbar. + * @type {boolean | number | Object} + */ + toolbar: true, + + /** + * Custom class name(s) to add to the viewer's root element. + * @type {string} + */ + className: '', + + /** + * Define where to put the viewer in modal mode. + * @type {string | Element} + */ + container: 'body', + + /** + * Filter the images for viewing. Return true if the image is viewable. + * @type {Function} + */ + filter: null, + + /** + * Enable to request fullscreen when play. + * @type {boolean} + */ + fullscreen: true, + + /** + * Define the initial index of image for viewing. + * @type {number} + */ + initialViewIndex: 0, + + /** + * Enable inline mode. + * @type {boolean} + */ + inline: false, + + /** + * The amount of time to delay between automatically cycling an image when playing. + * @type {number} + */ + interval: 5000, + + /** + * Enable keyboard support. + * @type {boolean} + */ + keyboard: true, + + /** + * Indicate if show a loading spinner when load image or not. + * @type {boolean} + */ + loading: true, + + /** + * Indicate if enable loop viewing or not. + * @type {boolean} + */ + loop: true, + + /** + * Min width of the viewer in inline mode. + * @type {number} + */ + minWidth: 200, + + /** + * Min height of the viewer in inline mode. + * @type {number} + */ + minHeight: 100, + + /** + * Enable to move the image. + * @type {boolean} + */ + movable: true, + + /** + * Enable to zoom the image. + * @type {boolean} + */ + zoomable: true, + + /** + * Enable to rotate the image. + * @type {boolean} + */ + rotatable: true, + + /** + * Enable to scale the image. + * @type {boolean} + */ + scalable: true, + + /** + * Indicate if toggle the image size between its natural size + * and initial size when double click on the image or not. + * @type {boolean} + */ + toggleOnDblclick: true, + + /** + * Show the tooltip with image ratio (percentage) when zoom in or zoom out. + * @type {boolean} + */ + tooltip: true, + + /** + * Enable CSS3 Transition for some special elements. + * @type {boolean} + */ + transition: true, + + /** + * Define the CSS `z-index` value of viewer in modal mode. + * @type {number} + */ + zIndex: 2015, + + /** + * Define the CSS `z-index` value of viewer in inline mode. + * @type {number} + */ + zIndexInline: 0, + + /** + * Define the ratio when zoom the image by wheeling mouse. + * @type {number} + */ + zoomRatio: 0.1, + + /** + * Define the min ratio of the image when zoom out. + * @type {number} + */ + minZoomRatio: 0.01, + + /** + * Define the max ratio of the image when zoom in. + * @type {number} + */ + maxZoomRatio: 100, + + /** + * Define where to get the original image URL for viewing. + * @type {string | Function} + */ + url: 'src', + + /** + * Event shortcuts. + * @type {Function} + */ + ready: null, + show: null, + shown: null, + hide: null, + hidden: null, + view: null, + viewed: null, + zoom: null, + zoomed: null +}; + +var TEMPLATE = '
      ' + '
      ' + '' + '
      ' + '
      ' + '
      ' + '
      '; + +var IS_BROWSER = typeof window !== 'undefined'; +var WINDOW = IS_BROWSER ? window : {}; +var IS_TOUCH_DEVICE = IS_BROWSER ? 'ontouchstart' in WINDOW.document.documentElement : false; +var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false; +var NAMESPACE = 'viewer'; // Actions + +var ACTION_MOVE = 'move'; +var ACTION_SWITCH = 'switch'; +var ACTION_ZOOM = 'zoom'; // Classes + +var CLASS_ACTIVE = "".concat(NAMESPACE, "-active"); +var CLASS_CLOSE = "".concat(NAMESPACE, "-close"); +var CLASS_FADE = "".concat(NAMESPACE, "-fade"); +var CLASS_FIXED = "".concat(NAMESPACE, "-fixed"); +var CLASS_FULLSCREEN = "".concat(NAMESPACE, "-fullscreen"); +var CLASS_FULLSCREEN_EXIT = "".concat(NAMESPACE, "-fullscreen-exit"); +var CLASS_HIDE = "".concat(NAMESPACE, "-hide"); +var CLASS_HIDE_MD_DOWN = "".concat(NAMESPACE, "-hide-md-down"); +var CLASS_HIDE_SM_DOWN = "".concat(NAMESPACE, "-hide-sm-down"); +var CLASS_HIDE_XS_DOWN = "".concat(NAMESPACE, "-hide-xs-down"); +var CLASS_IN = "".concat(NAMESPACE, "-in"); +var CLASS_INVISIBLE = "".concat(NAMESPACE, "-invisible"); +var CLASS_LOADING = "".concat(NAMESPACE, "-loading"); +var CLASS_MOVE = "".concat(NAMESPACE, "-move"); +var CLASS_OPEN = "".concat(NAMESPACE, "-open"); +var CLASS_SHOW = "".concat(NAMESPACE, "-show"); +var CLASS_TRANSITION = "".concat(NAMESPACE, "-transition"); // Events + +var EVENT_CLICK = 'click'; +var EVENT_DBLCLICK = 'dblclick'; +var EVENT_DRAG_START = 'dragstart'; +var EVENT_HIDDEN = 'hidden'; +var EVENT_HIDE = 'hide'; +var EVENT_KEY_DOWN = 'keydown'; +var EVENT_LOAD = 'load'; +var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown'; +var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove'; +var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup'; +var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START; +var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE; +var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END; +var EVENT_READY = 'ready'; +var EVENT_RESIZE = 'resize'; +var EVENT_SHOW = 'show'; +var EVENT_SHOWN = 'shown'; +var EVENT_TRANSITION_END = 'transitionend'; +var EVENT_VIEW = 'view'; +var EVENT_VIEWED = 'viewed'; +var EVENT_WHEEL = 'wheel'; +var EVENT_ZOOM = 'zoom'; +var EVENT_ZOOMED = 'zoomed'; // Data keys + +var DATA_ACTION = "".concat(NAMESPACE, "Action"); // RegExps + +var REGEXP_SPACES = /\s\s*/; // Misc + +var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical']; + +/** + * Check if the given value is a string. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a string, else `false`. + */ + +function isString(value) { + return typeof value === 'string'; +} +/** + * Check if the given value is not a number. + */ + +var isNaN = Number.isNaN || WINDOW.isNaN; +/** + * Check if the given value is a number. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a number, else `false`. + */ + +function isNumber(value) { + return typeof value === 'number' && !isNaN(value); +} +/** + * Check if the given value is undefined. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is undefined, else `false`. + */ + +function isUndefined(value) { + return typeof value === 'undefined'; +} +/** + * Check if the given value is an object. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is an object, else `false`. + */ + +function isObject(value) { + return _typeof(value) === 'object' && value !== null; +} +var hasOwnProperty = Object.prototype.hasOwnProperty; +/** + * Check if the given value is a plain object. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a plain object, else `false`. + */ + +function isPlainObject(value) { + if (!isObject(value)) { + return false; + } + + try { + var _constructor = value.constructor; + var prototype = _constructor.prototype; + return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf'); + } catch (error) { + return false; + } +} +/** + * Check if the given value is a function. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a function, else `false`. + */ + +function isFunction(value) { + return typeof value === 'function'; +} +/** + * Iterate the given data. + * @param {*} data - The data to iterate. + * @param {Function} callback - The process function for each element. + * @returns {*} The original data. + */ + +function forEach(data, callback) { + if (data && isFunction(callback)) { + if (Array.isArray(data) || isNumber(data.length) + /* array-like */ + ) { + var length = data.length; + var i; + + for (i = 0; i < length; i += 1) { + if (callback.call(data, data[i], i, data) === false) { + break; + } + } + } else if (isObject(data)) { + Object.keys(data).forEach(function (key) { + callback.call(data, data[key], key, data); + }); + } + } + + return data; +} +/** + * Extend the given object. + * @param {*} obj - The object to be extended. + * @param {*} args - The rest objects which will be merged to the first object. + * @returns {Object} The extended object. + */ + +var assign = Object.assign || function assign(obj) { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + if (isObject(obj) && args.length > 0) { + args.forEach(function (arg) { + if (isObject(arg)) { + Object.keys(arg).forEach(function (key) { + obj[key] = arg[key]; + }); + } + }); + } + + return obj; +}; +var REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/; +/** + * Apply styles to the given element. + * @param {Element} element - The target element. + * @param {Object} styles - The styles for applying. + */ + +function setStyle(element, styles) { + var style = element.style; + forEach(styles, function (value, property) { + if (REGEXP_SUFFIX.test(property) && isNumber(value)) { + value += 'px'; + } + + style[property] = value; + }); +} +/** + * Check if the given element has a special class. + * @param {Element} element - The element to check. + * @param {string} value - The class to search. + * @returns {boolean} Returns `true` if the special class was found. + */ + +function hasClass(element, value) { + return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1; +} +/** + * Add classes to the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be added. + */ + +function addClass(element, value) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + addClass(elem, value); + }); + return; + } + + if (element.classList) { + element.classList.add(value); + return; + } + + var className = element.className.trim(); + + if (!className) { + element.className = value; + } else if (className.indexOf(value) < 0) { + element.className = "".concat(className, " ").concat(value); + } +} +/** + * Remove classes from the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be removed. + */ + +function removeClass(element, value) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + removeClass(elem, value); + }); + return; + } + + if (element.classList) { + element.classList.remove(value); + return; + } + + if (element.className.indexOf(value) >= 0) { + element.className = element.className.replace(value, ''); + } +} +/** + * Add or remove classes from the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be toggled. + * @param {boolean} added - Add only. + */ + +function toggleClass(element, value, added) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + toggleClass(elem, value, added); + }); + return; + } // IE10-11 doesn't support the second parameter of `classList.toggle` + + + if (added) { + addClass(element, value); + } else { + removeClass(element, value); + } +} +var REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g; +/** + * Transform the given string from camelCase to kebab-case + * @param {string} value - The value to transform. + * @returns {string} The transformed value. + */ + +function hyphenate(value) { + return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase(); +} +/** + * Get data from the given element. + * @param {Element} element - The target element. + * @param {string} name - The data key to get. + * @returns {string} The data value. + */ + +function getData(element, name) { + if (isObject(element[name])) { + return element[name]; + } + + if (element.dataset) { + return element.dataset[name]; + } + + return element.getAttribute("data-".concat(hyphenate(name))); +} +/** + * Set data to the given element. + * @param {Element} element - The target element. + * @param {string} name - The data key to set. + * @param {string} data - The data value. + */ + +function setData(element, name, data) { + if (isObject(data)) { + element[name] = data; + } else if (element.dataset) { + element.dataset[name] = data; + } else { + element.setAttribute("data-".concat(hyphenate(name)), data); + } +} + +var onceSupported = function () { + var supported = false; + + if (IS_BROWSER) { + var once = false; + + var listener = function listener() {}; + + var options = Object.defineProperty({}, 'once', { + get: function get() { + supported = true; + return once; + }, + + /** + * This setter can fix a `TypeError` in strict mode + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only} + * @param {boolean} value - The value to set + */ + set: function set(value) { + once = value; + } + }); + WINDOW.addEventListener('test', listener, options); + WINDOW.removeEventListener('test', listener, options); + } + + return supported; +}(); +/** + * Remove event listener from the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Function} listener - The event listener. + * @param {Object} options - The event options. + */ + + +function removeListener(element, type, listener) { + var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + var handler = listener; + type.trim().split(REGEXP_SPACES).forEach(function (event) { + if (!onceSupported) { + var listeners = element.listeners; + + if (listeners && listeners[event] && listeners[event][listener]) { + handler = listeners[event][listener]; + delete listeners[event][listener]; + + if (Object.keys(listeners[event]).length === 0) { + delete listeners[event]; + } + + if (Object.keys(listeners).length === 0) { + delete element.listeners; + } + } + } + + element.removeEventListener(event, handler, options); + }); +} +/** + * Add event listener to the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Function} listener - The event listener. + * @param {Object} options - The event options. + */ + +function addListener(element, type, listener) { + var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + var _handler = listener; + type.trim().split(REGEXP_SPACES).forEach(function (event) { + if (options.once && !onceSupported) { + var _element$listeners = element.listeners, + listeners = _element$listeners === void 0 ? {} : _element$listeners; + + _handler = function handler() { + delete listeners[event][listener]; + element.removeEventListener(event, _handler, options); + + for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + listener.apply(element, args); + }; + + if (!listeners[event]) { + listeners[event] = {}; + } + + if (listeners[event][listener]) { + element.removeEventListener(event, listeners[event][listener], options); + } + + listeners[event][listener] = _handler; + element.listeners = listeners; + } + + element.addEventListener(event, _handler, options); + }); +} +/** + * Dispatch event on the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Object} data - The additional event data. + * @returns {boolean} Indicate if the event is default prevented or not. + */ + +function dispatchEvent(element, type, data) { + var event; // Event and CustomEvent on IE9-11 are global objects, not constructors + + if (isFunction(Event) && isFunction(CustomEvent)) { + event = new CustomEvent(type, { + detail: data, + bubbles: true, + cancelable: true + }); + } else { + event = document.createEvent('CustomEvent'); + event.initCustomEvent(type, true, true, data); + } + + return element.dispatchEvent(event); +} +/** + * Get the offset base on the document. + * @param {Element} element - The target element. + * @returns {Object} The offset data. + */ + +function getOffset(element) { + var box = element.getBoundingClientRect(); + return { + left: box.left + (window.pageXOffset - document.documentElement.clientLeft), + top: box.top + (window.pageYOffset - document.documentElement.clientTop) + }; +} +/** + * Get transforms base on the given object. + * @param {Object} obj - The target object. + * @returns {string} A string contains transform values. + */ + +function getTransforms(_ref) { + var rotate = _ref.rotate, + scaleX = _ref.scaleX, + scaleY = _ref.scaleY, + translateX = _ref.translateX, + translateY = _ref.translateY; + var values = []; + + if (isNumber(translateX) && translateX !== 0) { + values.push("translateX(".concat(translateX, "px)")); + } + + if (isNumber(translateY) && translateY !== 0) { + values.push("translateY(".concat(translateY, "px)")); + } // Rotate should come first before scale to match orientation transform + + + if (isNumber(rotate) && rotate !== 0) { + values.push("rotate(".concat(rotate, "deg)")); + } + + if (isNumber(scaleX) && scaleX !== 1) { + values.push("scaleX(".concat(scaleX, ")")); + } + + if (isNumber(scaleY) && scaleY !== 1) { + values.push("scaleY(".concat(scaleY, ")")); + } + + var transform = values.length ? values.join(' ') : 'none'; + return { + WebkitTransform: transform, + msTransform: transform, + transform: transform + }; +} +/** + * Get an image name from an image url. + * @param {string} url - The target url. + * @example + * // picture.jpg + * getImageNameFromURL('http://domain.com/path/to/picture.jpg?size=1280×960') + * @returns {string} A string contains the image name. + */ + +function getImageNameFromURL(url) { + return isString(url) ? url.replace(/^.*\//, '').replace(/[?&#].*$/, '') : ''; +} +var IS_SAFARI = WINDOW.navigator && /(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(WINDOW.navigator.userAgent); +/** + * Get an image's natural sizes. + * @param {string} image - The target image. + * @param {Function} callback - The callback function. + * @returns {HTMLImageElement} The new image. + */ + +function getImageNaturalSizes(image, callback) { + var newImage = document.createElement('img'); // Modern browsers (except Safari) + + if (image.naturalWidth && !IS_SAFARI) { + callback(image.naturalWidth, image.naturalHeight); + return newImage; + } + + var body = document.body || document.documentElement; + + newImage.onload = function () { + callback(newImage.width, newImage.height); + + if (!IS_SAFARI) { + body.removeChild(newImage); + } + }; + + newImage.src = image.src; // iOS Safari will convert the image automatically + // with its orientation once append it into DOM + + if (!IS_SAFARI) { + newImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;'; + body.appendChild(newImage); + } + + return newImage; +} +/** + * Get the related class name of a responsive type number. + * @param {string} type - The responsive type. + * @returns {string} The related class name. + */ + +function getResponsiveClass(type) { + switch (type) { + case 2: + return CLASS_HIDE_XS_DOWN; + + case 3: + return CLASS_HIDE_SM_DOWN; + + case 4: + return CLASS_HIDE_MD_DOWN; + + default: + return ''; + } +} +/** + * Get the max ratio of a group of pointers. + * @param {string} pointers - The target pointers. + * @returns {number} The result ratio. + */ + +function getMaxZoomRatio(pointers) { + var pointers2 = assign({}, pointers); + var ratios = []; + forEach(pointers, function (pointer, pointerId) { + delete pointers2[pointerId]; + forEach(pointers2, function (pointer2) { + var x1 = Math.abs(pointer.startX - pointer2.startX); + var y1 = Math.abs(pointer.startY - pointer2.startY); + var x2 = Math.abs(pointer.endX - pointer2.endX); + var y2 = Math.abs(pointer.endY - pointer2.endY); + var z1 = Math.sqrt(x1 * x1 + y1 * y1); + var z2 = Math.sqrt(x2 * x2 + y2 * y2); + var ratio = (z2 - z1) / z1; + ratios.push(ratio); + }); + }); + ratios.sort(function (a, b) { + return Math.abs(a) < Math.abs(b); + }); + return ratios[0]; +} +/** + * Get a pointer from an event object. + * @param {Object} event - The target event object. + * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not. + * @returns {Object} The result pointer contains start and/or end point coordinates. + */ + +function getPointer(_ref2, endOnly) { + var pageX = _ref2.pageX, + pageY = _ref2.pageY; + var end = { + endX: pageX, + endY: pageY + }; + return endOnly ? end : assign({ + timeStamp: Date.now(), + startX: pageX, + startY: pageY + }, end); +} +/** + * Get the center point coordinate of a group of pointers. + * @param {Object} pointers - The target pointers. + * @returns {Object} The center point coordinate. + */ + +function getPointersCenter(pointers) { + var pageX = 0; + var pageY = 0; + var count = 0; + forEach(pointers, function (_ref3) { + var startX = _ref3.startX, + startY = _ref3.startY; + pageX += startX; + pageY += startY; + count += 1; + }); + pageX /= count; + pageY /= count; + return { + pageX: pageX, + pageY: pageY + }; +} + +var render = { + render: function render() { + this.initContainer(); + this.initViewer(); + this.initList(); + this.renderViewer(); + }, + initContainer: function initContainer() { + this.containerData = { + width: window.innerWidth, + height: window.innerHeight + }; + }, + initViewer: function initViewer() { + var options = this.options, + parent = this.parent; + var viewerData; + + if (options.inline) { + viewerData = { + width: Math.max(parent.offsetWidth, options.minWidth), + height: Math.max(parent.offsetHeight, options.minHeight) + }; + this.parentData = viewerData; + } + + if (this.fulled || !viewerData) { + viewerData = this.containerData; + } + + this.viewerData = assign({}, viewerData); + }, + renderViewer: function renderViewer() { + if (this.options.inline && !this.fulled) { + setStyle(this.viewer, this.viewerData); + } + }, + initList: function initList() { + var _this = this; + + var element = this.element, + options = this.options, + list = this.list; + var items = []; + forEach(this.images, function (image, i) { + var src = image.src; + var alt = image.alt || getImageNameFromURL(src); + var url = options.url; + + if (isString(url)) { + url = image.getAttribute(url); + } else if (isFunction(url)) { + url = url.call(_this, image); + } + + if (src || url) { + items.push('
    • ' + '' + '
    • '); + } + }); + list.innerHTML = items.join(''); + this.items = list.getElementsByTagName('li'); + forEach(this.items, function (item) { + var image = item.firstElementChild; + setData(image, 'filled', true); + + if (options.loading) { + addClass(item, CLASS_LOADING); + } + + addListener(image, EVENT_LOAD, function (event) { + if (options.loading) { + removeClass(item, CLASS_LOADING); + } + + _this.loadImage(event); + }, { + once: true + }); + }); + + if (options.transition) { + addListener(element, EVENT_VIEWED, function () { + addClass(list, CLASS_TRANSITION); + }, { + once: true + }); + } + }, + renderList: function renderList(index) { + var i = index || this.index; + var width = this.items[i].offsetWidth || 30; + var outerWidth = width + 1; // 1 pixel of `margin-left` width + // Place the active item in the center of the screen + + setStyle(this.list, assign({ + width: outerWidth * this.length + }, getTransforms({ + translateX: (this.viewerData.width - width) / 2 - outerWidth * i + }))); + }, + resetList: function resetList() { + var list = this.list; + list.innerHTML = ''; + removeClass(list, CLASS_TRANSITION); + setStyle(list, getTransforms({ + translateX: 0 + })); + }, + initImage: function initImage(done) { + var _this2 = this; + + var options = this.options, + image = this.image, + viewerData = this.viewerData; + var footerHeight = this.footer.offsetHeight; + var viewerWidth = viewerData.width; + var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight); + var oldImageData = this.imageData || {}; + var sizingImage; + this.imageInitializing = { + abort: function abort() { + sizingImage.onload = null; + } + }; + sizingImage = getImageNaturalSizes(image, function (naturalWidth, naturalHeight) { + var aspectRatio = naturalWidth / naturalHeight; + var width = viewerWidth; + var height = viewerHeight; + _this2.imageInitializing = false; + + if (viewerHeight * aspectRatio > viewerWidth) { + height = viewerWidth / aspectRatio; + } else { + width = viewerHeight * aspectRatio; + } + + width = Math.min(width * 0.9, naturalWidth); + height = Math.min(height * 0.9, naturalHeight); + var imageData = { + naturalWidth: naturalWidth, + naturalHeight: naturalHeight, + aspectRatio: aspectRatio, + ratio: width / naturalWidth, + width: width, + height: height, + left: (viewerWidth - width) / 2, + top: (viewerHeight - height) / 2 + }; + var initialImageData = assign({}, imageData); + + if (options.rotatable) { + imageData.rotate = oldImageData.rotate || 0; + initialImageData.rotate = 0; + } + + if (options.scalable) { + imageData.scaleX = oldImageData.scaleX || 1; + imageData.scaleY = oldImageData.scaleY || 1; + initialImageData.scaleX = 1; + initialImageData.scaleY = 1; + } + + _this2.imageData = imageData; + _this2.initialImageData = initialImageData; + + if (done) { + done(); + } + }); + }, + renderImage: function renderImage(done) { + var _this3 = this; + + var image = this.image, + imageData = this.imageData; + setStyle(image, assign({ + width: imageData.width, + height: imageData.height, + // XXX: Not to use translateX/Y to avoid image shaking when zooming + marginLeft: imageData.left, + marginTop: imageData.top + }, getTransforms(imageData))); + + if (done) { + if ((this.viewing || this.zooming) && this.options.transition) { + var onTransitionEnd = function onTransitionEnd() { + _this3.imageRendering = false; + done(); + }; + + this.imageRendering = { + abort: function abort() { + removeListener(image, EVENT_TRANSITION_END, onTransitionEnd); + } + }; + addListener(image, EVENT_TRANSITION_END, onTransitionEnd, { + once: true + }); + } else { + done(); + } + } + }, + resetImage: function resetImage() { + // this.image only defined after viewed + if (this.viewing || this.viewed) { + var image = this.image; + + if (this.viewing) { + this.viewing.abort(); + } + + image.parentNode.removeChild(image); + this.image = null; + } + } +}; + +var events = { + bind: function bind() { + var options = this.options, + viewer = this.viewer, + canvas = this.canvas; + var document = this.element.ownerDocument; + addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this)); + addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), { + passive: false, + capture: true + }); + addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this)); + addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this)); + addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this)); + addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this)); + addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this)); + addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this)); + + if (options.toggleOnDblclick) { + addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this)); + } + }, + unbind: function unbind() { + var options = this.options, + viewer = this.viewer, + canvas = this.canvas; + var document = this.element.ownerDocument; + removeListener(viewer, EVENT_CLICK, this.onClick); + removeListener(viewer, EVENT_WHEEL, this.onWheel, { + passive: false, + capture: true + }); + removeListener(viewer, EVENT_DRAG_START, this.onDragStart); + removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown); + removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove); + removeListener(document, EVENT_POINTER_UP, this.onPointerUp); + removeListener(document, EVENT_KEY_DOWN, this.onKeyDown); + removeListener(window, EVENT_RESIZE, this.onResize); + + if (options.toggleOnDblclick) { + removeListener(canvas, EVENT_DBLCLICK, this.onDblclick); + } + } +}; + +var handlers = { + click: function click(event) { + var target = event.target; + var options = this.options, + imageData = this.imageData; + var action = getData(target, DATA_ACTION); // Cancel the emulated click when the native click event was triggered. + + if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) { + clearTimeout(this.clickCanvasTimeout); + } + + switch (action) { + case 'mix': + if (this.played) { + this.stop(); + } else if (options.inline) { + if (this.fulled) { + this.exit(); + } else { + this.full(); + } + } else { + this.hide(); + } + + break; + + case 'hide': + this.hide(); + break; + + case 'view': + this.view(getData(target, 'index')); + break; + + case 'zoom-in': + this.zoom(0.1, true); + break; + + case 'zoom-out': + this.zoom(-0.1, true); + break; + + case 'one-to-one': + this.toggle(); + break; + + case 'reset': + this.reset(); + break; + + case 'prev': + this.prev(options.loop); + break; + + case 'play': + this.play(options.fullscreen); + break; + + case 'next': + this.next(options.loop); + break; + + case 'rotate-left': + this.rotate(-90); + break; + + case 'rotate-right': + this.rotate(90); + break; + + case 'flip-horizontal': + this.scaleX(-imageData.scaleX || -1); + break; + + case 'flip-vertical': + this.scaleY(-imageData.scaleY || -1); + break; + + default: + if (this.played) { + this.stop(); + } + + } + }, + dblclick: function dblclick(event) { + event.preventDefault(); + + if (this.viewed && event.target === this.image) { + // Cancel the emulated double click when the native dblclick event was triggered. + if (IS_TOUCH_DEVICE && event.isTrusted) { + clearTimeout(this.doubleClickImageTimeout); + } + + this.toggle(); + } + }, + load: function load() { + var _this = this; + + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = false; + } + + var element = this.element, + options = this.options, + image = this.image, + index = this.index, + viewerData = this.viewerData; + removeClass(image, CLASS_INVISIBLE); + + if (options.loading) { + removeClass(this.canvas, CLASS_LOADING); + } + + image.style.cssText = 'height:0;' + "margin-left:".concat(viewerData.width / 2, "px;") + "margin-top:".concat(viewerData.height / 2, "px;") + 'max-width:none!important;' + 'position:absolute;' + 'width:0;'; + this.initImage(function () { + toggleClass(image, CLASS_MOVE, options.movable); + toggleClass(image, CLASS_TRANSITION, options.transition); + + _this.renderImage(function () { + _this.viewed = true; + _this.viewing = false; + + if (isFunction(options.viewed)) { + addListener(element, EVENT_VIEWED, options.viewed, { + once: true + }); + } + + dispatchEvent(element, EVENT_VIEWED, { + originalImage: _this.images[index], + index: index, + image: image + }); + }); + }); + }, + loadImage: function loadImage(event) { + var image = event.target; + var parent = image.parentNode; + var parentWidth = parent.offsetWidth || 30; + var parentHeight = parent.offsetHeight || 50; + var filled = !!getData(image, 'filled'); + getImageNaturalSizes(image, function (naturalWidth, naturalHeight) { + var aspectRatio = naturalWidth / naturalHeight; + var width = parentWidth; + var height = parentHeight; + + if (parentHeight * aspectRatio > parentWidth) { + if (filled) { + width = parentHeight * aspectRatio; + } else { + height = parentWidth / aspectRatio; + } + } else if (filled) { + height = parentWidth / aspectRatio; + } else { + width = parentHeight * aspectRatio; + } + + setStyle(image, assign({ + width: width, + height: height + }, getTransforms({ + translateX: (parentWidth - width) / 2, + translateY: (parentHeight - height) / 2 + }))); + }); + }, + keydown: function keydown(event) { + var options = this.options; + + if (!this.fulled || !options.keyboard) { + return; + } + + switch (event.keyCode || event.which || event.charCode) { + // Escape + case 27: + if (this.played) { + this.stop(); + } else if (options.inline) { + if (this.fulled) { + this.exit(); + } + } else { + this.hide(); + } + + break; + // Space + + case 32: + if (this.played) { + this.stop(); + } + + break; + // ArrowLeft + + case 37: + this.prev(options.loop); + break; + // ArrowUp + + case 38: + // Prevent scroll on Firefox + event.preventDefault(); // Zoom in + + this.zoom(options.zoomRatio, true); + break; + // ArrowRight + + case 39: + this.next(options.loop); + break; + // ArrowDown + + case 40: + // Prevent scroll on Firefox + event.preventDefault(); // Zoom out + + this.zoom(-options.zoomRatio, true); + break; + // Ctrl + 0 + + case 48: // Fall through + // Ctrl + 1 + // eslint-disable-next-line no-fallthrough + + case 49: + if (event.ctrlKey) { + event.preventDefault(); + this.toggle(); + } + + break; + + default: + } + }, + dragstart: function dragstart(event) { + if (event.target.tagName.toLowerCase() === 'img') { + event.preventDefault(); + } + }, + pointerdown: function pointerdown(event) { + var options = this.options, + pointers = this.pointers; + var buttons = event.buttons, + button = event.button; + + if (!this.viewed || this.showing || this.viewing || this.hiding // No primary button (Usually the left button) + // Note that touch events have no `buttons` or `button` property + || isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0 // Open context menu + || event.ctrlKey) { + return; + } // Prevent default behaviours as page zooming in touch devices. + + + event.preventDefault(); + + if (event.changedTouches) { + forEach(event.changedTouches, function (touch) { + pointers[touch.identifier] = getPointer(touch); + }); + } else { + pointers[event.pointerId || 0] = getPointer(event); + } + + var action = options.movable ? ACTION_MOVE : false; + + if (Object.keys(pointers).length > 1) { + action = ACTION_ZOOM; + } else if ((event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) { + action = ACTION_SWITCH; + } + + if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) { + removeClass(this.image, CLASS_TRANSITION); + } + + this.action = action; + }, + pointermove: function pointermove(event) { + var pointers = this.pointers, + action = this.action; + + if (!this.viewed || !action) { + return; + } + + event.preventDefault(); + + if (event.changedTouches) { + forEach(event.changedTouches, function (touch) { + assign(pointers[touch.identifier] || {}, getPointer(touch, true)); + }); + } else { + assign(pointers[event.pointerId || 0] || {}, getPointer(event, true)); + } + + this.change(event); + }, + pointerup: function pointerup(event) { + var _this2 = this; + + var options = this.options, + action = this.action, + pointers = this.pointers; + var pointer; + + if (event.changedTouches) { + forEach(event.changedTouches, function (touch) { + pointer = pointers[touch.identifier]; + delete pointers[touch.identifier]; + }); + } else { + pointer = pointers[event.pointerId || 0]; + delete pointers[event.pointerId || 0]; + } + + if (!action) { + return; + } + + event.preventDefault(); + + if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) { + addClass(this.image, CLASS_TRANSITION); + } + + this.action = false; // Emulate click and double click in touch devices to support backdrop and image zooming (#210). + + if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) { + clearTimeout(this.clickCanvasTimeout); + clearTimeout(this.doubleClickImageTimeout); + + if (options.toggleOnDblclick && this.viewed && event.target === this.image) { + if (this.imageClicked) { + this.imageClicked = false; // This timeout will be cleared later when a native dblclick event is triggering + + this.doubleClickImageTimeout = setTimeout(function () { + dispatchEvent(_this2.image, EVENT_DBLCLICK); + }, 50); + } else { + this.imageClicked = true; // The default timing of a double click in Windows is 500 ms + + this.doubleClickImageTimeout = setTimeout(function () { + _this2.imageClicked = false; + }, 500); + } + } else { + this.imageClicked = false; + + if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) { + // This timeout will be cleared later when a native click event is triggering + this.clickCanvasTimeout = setTimeout(function () { + dispatchEvent(_this2.canvas, EVENT_CLICK); + }, 50); + } + } + } + }, + resize: function resize() { + var _this3 = this; + + if (!this.isShown || this.hiding) { + return; + } + + this.initContainer(); + this.initViewer(); + this.renderViewer(); + this.renderList(); + + if (this.viewed) { + this.initImage(function () { + _this3.renderImage(); + }); + } + + if (this.played) { + if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { + this.stop(); + return; + } + + forEach(this.player.getElementsByTagName('img'), function (image) { + addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), { + once: true + }); + dispatchEvent(image, EVENT_LOAD); + }); + } + }, + wheel: function wheel(event) { + var _this4 = this; + + if (!this.viewed) { + return; + } + + event.preventDefault(); // Limit wheel speed to prevent zoom too fast + + if (this.wheeling) { + return; + } + + this.wheeling = true; + setTimeout(function () { + _this4.wheeling = false; + }, 50); + var ratio = Number(this.options.zoomRatio) || 0.1; + var delta = 1; + + if (event.deltaY) { + delta = event.deltaY > 0 ? 1 : -1; + } else if (event.wheelDelta) { + delta = -event.wheelDelta / 120; + } else if (event.detail) { + delta = event.detail > 0 ? 1 : -1; + } + + this.zoom(-delta * ratio, true, event); + } +}; + +var methods = { + /** Show the viewer (only available in modal mode) + * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not. + * @returns {Viewer} this + */ + show: function show() { + var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var element = this.element, + options = this.options; + + if (options.inline || this.showing || this.isShown || this.showing) { + return this; + } + + if (!this.ready) { + this.build(); + + if (this.ready) { + this.show(immediate); + } + + return this; + } + + if (isFunction(options.show)) { + addListener(element, EVENT_SHOW, options.show, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) { + return this; + } + + if (this.hiding) { + this.transitioning.abort(); + } + + this.showing = true; + this.open(); + var viewer = this.viewer; + removeClass(viewer, CLASS_HIDE); + + if (options.transition && !immediate) { + var shown = this.shown.bind(this); + this.transitioning = { + abort: function abort() { + removeListener(viewer, EVENT_TRANSITION_END, shown); + removeClass(viewer, CLASS_IN); + } + }; + addClass(viewer, CLASS_TRANSITION); // Force reflow to enable CSS3 transition + // eslint-disable-next-line + + viewer.offsetWidth; + addListener(viewer, EVENT_TRANSITION_END, shown, { + once: true + }); + addClass(viewer, CLASS_IN); + } else { + addClass(viewer, CLASS_IN); + this.shown(); + } + + return this; + }, + + /** + * Hide the viewer (only available in modal mode) + * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not. + * @returns {Viewer} this + */ + hide: function hide() { + var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var element = this.element, + options = this.options; + + if (options.inline || this.hiding || !(this.isShown || this.showing)) { + return this; + } + + if (isFunction(options.hide)) { + addListener(element, EVENT_HIDE, options.hide, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_HIDE) === false) { + return this; + } + + if (this.showing) { + this.transitioning.abort(); + } + + this.hiding = true; + + if (this.played) { + this.stop(); + } else if (this.viewing) { + this.viewing.abort(); + } + + var viewer = this.viewer; + + if (options.transition && !immediate) { + var hidden = this.hidden.bind(this); + + var hide = function hide() { + addListener(viewer, EVENT_TRANSITION_END, hidden, { + once: true + }); + removeClass(viewer, CLASS_IN); + }; + + this.transitioning = { + abort: function abort() { + if (this.viewed) { + removeListener(this.image, EVENT_TRANSITION_END, hide); + } else { + removeListener(viewer, EVENT_TRANSITION_END, hidden); + } + } + }; // Note that the `CLASS_TRANSITION` class will be removed on pointer down (#255) + + if (this.viewed && hasClass(this.image, CLASS_TRANSITION)) { + addListener(this.image, EVENT_TRANSITION_END, hide, { + once: true + }); + this.zoomTo(0, false, false, true); + } else { + hide(); + } + } else { + removeClass(viewer, CLASS_IN); + this.hidden(); + } + + return this; + }, + + /** + * View one of the images with image's index + * @param {number} index - The index of the image to view. + * @returns {Viewer} this + */ + view: function view() { + var _this = this; + + var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex; + index = Number(index) || 0; + + if (!this.isShown) { + this.index = index; + return this.show(); + } + + if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) { + return this; + } + + if (this.viewing) { + this.viewing.abort(); + } + + var element = this.element, + options = this.options, + title = this.title, + canvas = this.canvas; + var item = this.items[index]; + var img = item.querySelector('img'); + var url = getData(img, 'originalUrl'); + var alt = img.getAttribute('alt'); + var image = document.createElement('img'); + image.src = url; + image.alt = alt; + + if (isFunction(options.view)) { + addListener(element, EVENT_VIEW, options.view, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_VIEW, { + originalImage: this.images[index], + index: index, + image: image + }) === false || !this.isShown || this.hiding || this.played) { + return this; + } + + this.image = image; + removeClass(this.items[this.index], CLASS_ACTIVE); + addClass(item, CLASS_ACTIVE); + this.viewed = false; + this.index = index; + this.imageData = {}; + addClass(image, CLASS_INVISIBLE); + + if (options.loading) { + addClass(canvas, CLASS_LOADING); + } + + canvas.innerHTML = ''; + canvas.appendChild(image); // Center current item + + this.renderList(); // Clear title + + title.innerHTML = ''; // Generate title after viewed + + var onViewed = function onViewed() { + var imageData = _this.imageData; + var render = Array.isArray(options.title) ? options.title[1] : options.title; + title.innerHTML = isFunction(render) ? render.call(_this, image, imageData) : "".concat(alt, " (").concat(imageData.naturalWidth, " \xD7 ").concat(imageData.naturalHeight, ")"); + }; + + var onLoad; + addListener(element, EVENT_VIEWED, onViewed, { + once: true + }); + this.viewing = { + abort: function abort() { + removeListener(element, EVENT_VIEWED, onViewed); + + if (image.complete) { + if (this.imageRendering) { + this.imageRendering.abort(); + } else if (this.imageInitializing) { + this.imageInitializing.abort(); + } + } else { + // Cancel download to save bandwidth. + image.src = ''; + removeListener(image, EVENT_LOAD, onLoad); + + if (this.timeout) { + clearTimeout(this.timeout); + } + } + } + }; + + if (image.complete) { + this.load(); + } else { + addListener(image, EVENT_LOAD, onLoad = this.load.bind(this), { + once: true + }); + + if (this.timeout) { + clearTimeout(this.timeout); + } // Make the image visible if it fails to load within 1s + + + this.timeout = setTimeout(function () { + removeClass(image, CLASS_INVISIBLE); + _this.timeout = false; + }, 1000); + } + + return this; + }, + + /** + * View the previous image + * @param {boolean} [loop=false] - Indicate if view the last one + * when it is the first one at present. + * @returns {Viewer} this + */ + prev: function prev() { + var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var index = this.index - 1; + + if (index < 0) { + index = loop ? this.length - 1 : 0; + } + + this.view(index); + return this; + }, + + /** + * View the next image + * @param {boolean} [loop=false] - Indicate if view the first one + * when it is the last one at present. + * @returns {Viewer} this + */ + next: function next() { + var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var maxIndex = this.length - 1; + var index = this.index + 1; + + if (index > maxIndex) { + index = loop ? 0 : maxIndex; + } + + this.view(index); + return this; + }, + + /** + * Move the image with relative offsets. + * @param {number} offsetX - The relative offset distance on the x-axis. + * @param {number} offsetY - The relative offset distance on the y-axis. + * @returns {Viewer} this + */ + move: function move(offsetX, offsetY) { + var imageData = this.imageData; + this.moveTo(isUndefined(offsetX) ? offsetX : imageData.left + Number(offsetX), isUndefined(offsetY) ? offsetY : imageData.top + Number(offsetY)); + return this; + }, + + /** + * Move the image to an absolute point. + * @param {number} x - The x-axis coordinate. + * @param {number} [y=x] - The y-axis coordinate. + * @returns {Viewer} this + */ + moveTo: function moveTo(x) { + var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x; + var imageData = this.imageData; + x = Number(x); + y = Number(y); + + if (this.viewed && !this.played && this.options.movable) { + var changed = false; + + if (isNumber(x)) { + imageData.left = x; + changed = true; + } + + if (isNumber(y)) { + imageData.top = y; + changed = true; + } + + if (changed) { + this.renderImage(); + } + } + + return this; + }, + + /** + * Zoom the image with a relative ratio. + * @param {number} ratio - The target ratio. + * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not. + * @param {Event} [_originalEvent=null] - The original event if any. + * @returns {Viewer} this + */ + zoom: function zoom(ratio) { + var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + + var imageData = this.imageData; + ratio = Number(ratio); + + if (ratio < 0) { + ratio = 1 / (1 - ratio); + } else { + ratio = 1 + ratio; + } + + this.zoomTo(imageData.width * ratio / imageData.naturalWidth, hasTooltip, _originalEvent); + return this; + }, + + /** + * Zoom the image to an absolute ratio. + * @param {number} ratio - The target ratio. + * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not. + * @param {Event} [_originalEvent=null] - The original event if any. + * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not. + * @returns {Viewer} this + */ + zoomTo: function zoomTo(ratio) { + var _this2 = this; + + var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + + var _zoomable = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + + var element = this.element, + options = this.options, + pointers = this.pointers, + imageData = this.imageData; + var width = imageData.width, + height = imageData.height, + left = imageData.left, + top = imageData.top, + naturalWidth = imageData.naturalWidth, + naturalHeight = imageData.naturalHeight; + ratio = Math.max(0, ratio); + + if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) { + if (!_zoomable) { + var minZoomRatio = Math.max(0.01, options.minZoomRatio); + var maxZoomRatio = Math.min(100, options.maxZoomRatio); + ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio); + } + + if (_originalEvent && ratio > 0.95 && ratio < 1.05) { + ratio = 1; + } + + var newWidth = naturalWidth * ratio; + var newHeight = naturalHeight * ratio; + var offsetWidth = newWidth - width; + var offsetHeight = newHeight - height; + var oldRatio = width / naturalWidth; + + if (isFunction(options.zoom)) { + addListener(element, EVENT_ZOOM, options.zoom, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_ZOOM, { + ratio: ratio, + oldRatio: oldRatio, + originalEvent: _originalEvent + }) === false) { + return this; + } + + this.zooming = true; + + if (_originalEvent) { + var offset = getOffset(this.viewer); + var center = pointers && Object.keys(pointers).length ? getPointersCenter(pointers) : { + pageX: _originalEvent.pageX, + pageY: _originalEvent.pageY + }; // Zoom from the triggering point of the event + + imageData.left -= offsetWidth * ((center.pageX - offset.left - left) / width); + imageData.top -= offsetHeight * ((center.pageY - offset.top - top) / height); + } else { + // Zoom from the center of the image + imageData.left -= offsetWidth / 2; + imageData.top -= offsetHeight / 2; + } + + imageData.width = newWidth; + imageData.height = newHeight; + imageData.ratio = ratio; + this.renderImage(function () { + _this2.zooming = false; + + if (isFunction(options.zoomed)) { + addListener(element, EVENT_ZOOMED, options.zoomed, { + once: true + }); + } + + dispatchEvent(element, EVENT_ZOOMED, { + ratio: ratio, + oldRatio: oldRatio, + originalEvent: _originalEvent + }); + }); + + if (hasTooltip) { + this.tooltip(); + } + } + + return this; + }, + + /** + * Rotate the image with a relative degree. + * @param {number} degree - The rotate degree. + * @returns {Viewer} this + */ + rotate: function rotate(degree) { + this.rotateTo((this.imageData.rotate || 0) + Number(degree)); + return this; + }, + + /** + * Rotate the image to an absolute degree. + * @param {number} degree - The rotate degree. + * @returns {Viewer} this + */ + rotateTo: function rotateTo(degree) { + var imageData = this.imageData; + degree = Number(degree); + + if (isNumber(degree) && this.viewed && !this.played && this.options.rotatable) { + imageData.rotate = degree; + this.renderImage(); + } + + return this; + }, + + /** + * Scale the image on the x-axis. + * @param {number} scaleX - The scale ratio on the x-axis. + * @returns {Viewer} this + */ + scaleX: function scaleX(_scaleX) { + this.scale(_scaleX, this.imageData.scaleY); + return this; + }, + + /** + * Scale the image on the y-axis. + * @param {number} scaleY - The scale ratio on the y-axis. + * @returns {Viewer} this + */ + scaleY: function scaleY(_scaleY) { + this.scale(this.imageData.scaleX, _scaleY); + return this; + }, + + /** + * Scale the image. + * @param {number} scaleX - The scale ratio on the x-axis. + * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis. + * @returns {Viewer} this + */ + scale: function scale(scaleX) { + var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX; + var imageData = this.imageData; + scaleX = Number(scaleX); + scaleY = Number(scaleY); + + if (this.viewed && !this.played && this.options.scalable) { + var changed = false; + + if (isNumber(scaleX)) { + imageData.scaleX = scaleX; + changed = true; + } + + if (isNumber(scaleY)) { + imageData.scaleY = scaleY; + changed = true; + } + + if (changed) { + this.renderImage(); + } + } + + return this; + }, + + /** + * Play the images + * @param {boolean} [fullscreen=false] - Indicate if request fullscreen or not. + * @returns {Viewer} this + */ + play: function play() { + var _this3 = this; + + var fullscreen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + + if (!this.isShown || this.played) { + return this; + } + + var options = this.options, + player = this.player; + var onLoad = this.loadImage.bind(this); + var list = []; + var total = 0; + var index = 0; + this.played = true; + this.onLoadWhenPlay = onLoad; + + if (fullscreen) { + this.requestFullscreen(); + } + + addClass(player, CLASS_SHOW); + forEach(this.items, function (item, i) { + var img = item.querySelector('img'); + var image = document.createElement('img'); + image.src = getData(img, 'originalUrl'); + image.alt = img.getAttribute('alt'); + total += 1; + addClass(image, CLASS_FADE); + toggleClass(image, CLASS_TRANSITION, options.transition); + + if (hasClass(item, CLASS_ACTIVE)) { + addClass(image, CLASS_IN); + index = i; + } + + list.push(image); + addListener(image, EVENT_LOAD, onLoad, { + once: true + }); + player.appendChild(image); + }); + + if (isNumber(options.interval) && options.interval > 0) { + var play = function play() { + _this3.playing = setTimeout(function () { + removeClass(list[index], CLASS_IN); + index += 1; + index = index < total ? index : 0; + addClass(list[index], CLASS_IN); + play(); + }, options.interval); + }; + + if (total > 1) { + play(); + } + } + + return this; + }, + // Stop play + stop: function stop() { + var _this4 = this; + + if (!this.played) { + return this; + } + + var player = this.player; + this.played = false; + clearTimeout(this.playing); + forEach(player.getElementsByTagName('img'), function (image) { + removeListener(image, EVENT_LOAD, _this4.onLoadWhenPlay); + }); + removeClass(player, CLASS_SHOW); + player.innerHTML = ''; + this.exitFullscreen(); + return this; + }, + // Enter modal mode (only available in inline mode) + full: function full() { + var _this5 = this; + + var options = this.options, + viewer = this.viewer, + image = this.image, + list = this.list; + + if (!this.isShown || this.played || this.fulled || !options.inline) { + return this; + } + + this.fulled = true; + this.open(); + addClass(this.button, CLASS_FULLSCREEN_EXIT); + + if (options.transition) { + removeClass(list, CLASS_TRANSITION); + + if (this.viewed) { + removeClass(image, CLASS_TRANSITION); + } + } + + addClass(viewer, CLASS_FIXED); + viewer.setAttribute('style', ''); + setStyle(viewer, { + zIndex: options.zIndex + }); + this.initContainer(); + this.viewerData = assign({}, this.containerData); + this.renderList(); + + if (this.viewed) { + this.initImage(function () { + _this5.renderImage(function () { + if (options.transition) { + setTimeout(function () { + addClass(image, CLASS_TRANSITION); + addClass(list, CLASS_TRANSITION); + }, 0); + } + }); + }); + } + + return this; + }, + // Exit modal mode (only available in inline mode) + exit: function exit() { + var _this6 = this; + + var options = this.options, + viewer = this.viewer, + image = this.image, + list = this.list; + + if (!this.isShown || this.played || !this.fulled || !options.inline) { + return this; + } + + this.fulled = false; + this.close(); + removeClass(this.button, CLASS_FULLSCREEN_EXIT); + + if (options.transition) { + removeClass(list, CLASS_TRANSITION); + + if (this.viewed) { + removeClass(image, CLASS_TRANSITION); + } + } + + removeClass(viewer, CLASS_FIXED); + setStyle(viewer, { + zIndex: options.zIndexInline + }); + this.viewerData = assign({}, this.parentData); + this.renderViewer(); + this.renderList(); + + if (this.viewed) { + this.initImage(function () { + _this6.renderImage(function () { + if (options.transition) { + setTimeout(function () { + addClass(image, CLASS_TRANSITION); + addClass(list, CLASS_TRANSITION); + }, 0); + } + }); + }); + } + + return this; + }, + // Show the current ratio of the image with percentage + tooltip: function tooltip() { + var _this7 = this; + + var options = this.options, + tooltipBox = this.tooltipBox, + imageData = this.imageData; + + if (!this.viewed || this.played || !options.tooltip) { + return this; + } + + tooltipBox.textContent = "".concat(Math.round(imageData.ratio * 100), "%"); + + if (!this.tooltipping) { + if (options.transition) { + if (this.fading) { + dispatchEvent(tooltipBox, EVENT_TRANSITION_END); + } + + addClass(tooltipBox, CLASS_SHOW); + addClass(tooltipBox, CLASS_FADE); + addClass(tooltipBox, CLASS_TRANSITION); // Force reflow to enable CSS3 transition + // eslint-disable-next-line + + tooltipBox.offsetWidth; + addClass(tooltipBox, CLASS_IN); + } else { + addClass(tooltipBox, CLASS_SHOW); + } + } else { + clearTimeout(this.tooltipping); + } + + this.tooltipping = setTimeout(function () { + if (options.transition) { + addListener(tooltipBox, EVENT_TRANSITION_END, function () { + removeClass(tooltipBox, CLASS_SHOW); + removeClass(tooltipBox, CLASS_FADE); + removeClass(tooltipBox, CLASS_TRANSITION); + _this7.fading = false; + }, { + once: true + }); + removeClass(tooltipBox, CLASS_IN); + _this7.fading = true; + } else { + removeClass(tooltipBox, CLASS_SHOW); + } + + _this7.tooltipping = false; + }, 1000); + return this; + }, + // Toggle the image size between its natural size and initial size + toggle: function toggle() { + if (this.imageData.ratio === 1) { + this.zoomTo(this.initialImageData.ratio, true); + } else { + this.zoomTo(1, true); + } + + return this; + }, + // Reset the image to its initial state + reset: function reset() { + if (this.viewed && !this.played) { + this.imageData = assign({}, this.initialImageData); + this.renderImage(); + } + + return this; + }, + // Update viewer when images changed + update: function update() { + var element = this.element, + options = this.options, + isImg = this.isImg; // Destroy viewer if the target image was deleted + + if (isImg && !element.parentNode) { + return this.destroy(); + } + + var images = []; + forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) { + if (options.filter) { + if (options.filter(image)) { + images.push(image); + } + } else { + images.push(image); + } + }); + + if (!images.length) { + return this; + } + + this.images = images; + this.length = images.length; + + if (this.ready) { + var indexes = []; + forEach(this.items, function (item, i) { + var img = item.querySelector('img'); + var image = images[i]; + + if (image) { + if (image.src !== img.src) { + indexes.push(i); + } + } else { + indexes.push(i); + } + }); + setStyle(this.list, { + width: 'auto' + }); + this.initList(); + + if (this.isShown) { + if (this.length) { + if (this.viewed) { + var index = indexes.indexOf(this.index); + + if (index >= 0) { + this.viewed = false; + this.view(Math.max(this.index - (index + 1), 0)); + } else { + addClass(this.items[this.index], CLASS_ACTIVE); + } + } + } else { + this.image = null; + this.viewed = false; + this.index = 0; + this.imageData = {}; + this.canvas.innerHTML = ''; + this.title.innerHTML = ''; + } + } + } else { + this.build(); + } + + return this; + }, + // Destroy the viewer + destroy: function destroy() { + var element = this.element, + options = this.options; + + if (!element[NAMESPACE]) { + return this; + } + + this.destroyed = true; + + if (this.ready) { + if (this.played) { + this.stop(); + } + + if (options.inline) { + if (this.fulled) { + this.exit(); + } + + this.unbind(); + } else if (this.isShown) { + if (this.viewing) { + if (this.imageRendering) { + this.imageRendering.abort(); + } else if (this.imageInitializing) { + this.imageInitializing.abort(); + } + } + + if (this.hiding) { + this.transitioning.abort(); + } + + this.hidden(); + } else if (this.showing) { + this.transitioning.abort(); + this.hidden(); + } + + this.ready = false; + this.viewer.parentNode.removeChild(this.viewer); + } else if (options.inline) { + if (this.delaying) { + this.delaying.abort(); + } else if (this.initializing) { + this.initializing.abort(); + } + } + + if (!options.inline) { + removeListener(element, EVENT_CLICK, this.onStart); + } + + element[NAMESPACE] = undefined; + return this; + } +}; + +var others = { + open: function open() { + var body = this.body; + addClass(body, CLASS_OPEN); + body.style.paddingRight = "".concat(this.scrollbarWidth + (parseFloat(this.initialBodyPaddingRight) || 0), "px"); + }, + close: function close() { + var body = this.body; + removeClass(body, CLASS_OPEN); + body.style.paddingRight = this.initialBodyPaddingRight; + }, + shown: function shown() { + var element = this.element, + options = this.options; + this.fulled = true; + this.isShown = true; + this.render(); + this.bind(); + this.showing = false; + + if (isFunction(options.shown)) { + addListener(element, EVENT_SHOWN, options.shown, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_SHOWN) === false) { + return; + } + + if (this.ready && this.isShown && !this.hiding) { + this.view(this.index); + } + }, + hidden: function hidden() { + var element = this.element, + options = this.options; + this.fulled = false; + this.viewed = false; + this.isShown = false; + this.close(); + this.unbind(); + addClass(this.viewer, CLASS_HIDE); + this.resetList(); + this.resetImage(); + this.hiding = false; + + if (!this.destroyed) { + if (isFunction(options.hidden)) { + addListener(element, EVENT_HIDDEN, options.hidden, { + once: true + }); + } + + dispatchEvent(element, EVENT_HIDDEN); + } + }, + requestFullscreen: function requestFullscreen() { + var document = this.element.ownerDocument; + + if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { + var documentElement = document.documentElement; // Element.requestFullscreen() + + if (documentElement.requestFullscreen) { + documentElement.requestFullscreen(); + } else if (documentElement.webkitRequestFullscreen) { + documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else if (documentElement.mozRequestFullScreen) { + documentElement.mozRequestFullScreen(); + } else if (documentElement.msRequestFullscreen) { + documentElement.msRequestFullscreen(); + } + } + }, + exitFullscreen: function exitFullscreen() { + var document = this.element.ownerDocument; + + if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { + // Document.exitFullscreen() + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } + } + }, + change: function change(event) { + var options = this.options, + pointers = this.pointers; + var pointer = pointers[Object.keys(pointers)[0]]; + var offsetX = pointer.endX - pointer.startX; + var offsetY = pointer.endY - pointer.startY; + + switch (this.action) { + // Move the current image + case ACTION_MOVE: + this.move(offsetX, offsetY); + break; + // Zoom the current image + + case ACTION_ZOOM: + this.zoom(getMaxZoomRatio(pointers), false, event); + break; + + case ACTION_SWITCH: + { + this.action = 'switched'; + var absoluteOffsetX = Math.abs(offsetX); + + if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) { + // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers. + this.pointers = {}; + + if (offsetX > 1) { + this.prev(options.loop); + } else if (offsetX < -1) { + this.next(options.loop); + } + } + + break; + } + + default: + } // Override + + + forEach(pointers, function (p) { + p.startX = p.endX; + p.startY = p.endY; + }); + }, + isSwitchable: function isSwitchable() { + var imageData = this.imageData, + viewerData = this.viewerData; + return this.length > 1 && imageData.left >= 0 && imageData.top >= 0 && imageData.width <= viewerData.width && imageData.height <= viewerData.height; + } +}; + +var AnotherViewer = WINDOW.Viewer; + +var Viewer = +/*#__PURE__*/ +function () { + /** + * Create a new Viewer. + * @param {Element} element - The target element for viewing. + * @param {Object} [options={}] - The configuration options. + */ + function Viewer(element) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, Viewer); + + if (!element || element.nodeType !== 1) { + throw new Error('The first argument is required and must be an element.'); + } + + this.element = element; + this.options = assign({}, DEFAULTS, isPlainObject(options) && options); + this.action = false; + this.fading = false; + this.fulled = false; + this.hiding = false; + this.imageClicked = false; + this.imageData = {}; + this.index = this.options.initialViewIndex; + this.isImg = false; + this.isShown = false; + this.length = 0; + this.played = false; + this.playing = false; + this.pointers = {}; + this.ready = false; + this.showing = false; + this.timeout = false; + this.tooltipping = false; + this.viewed = false; + this.viewing = false; + this.wheeling = false; + this.zooming = false; + this.init(); + } + + _createClass(Viewer, [{ + key: "init", + value: function init() { + var _this = this; + + var element = this.element, + options = this.options; + + if (element[NAMESPACE]) { + return; + } + + element[NAMESPACE] = this; + var isImg = element.tagName.toLowerCase() === 'img'; + var images = []; + forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) { + if (isFunction(options.filter)) { + if (options.filter.call(_this, image)) { + images.push(image); + } + } else { + images.push(image); + } + }); + this.isImg = isImg; + this.length = images.length; + this.images = images; + var ownerDocument = element.ownerDocument; + var body = ownerDocument.body || ownerDocument.documentElement; + this.body = body; + this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth; + this.initialBodyPaddingRight = window.getComputedStyle(body).paddingRight; // Override `transition` option if it is not supported + + if (isUndefined(document.createElement(NAMESPACE).style.transition)) { + options.transition = false; + } + + if (options.inline) { + var count = 0; + + var progress = function progress() { + count += 1; + + if (count === _this.length) { + var timeout; + _this.initializing = false; + _this.delaying = { + abort: function abort() { + clearTimeout(timeout); + } + }; // build asynchronously to keep `this.viewer` is accessible in `ready` event handler. + + timeout = setTimeout(function () { + _this.delaying = false; + + _this.build(); + }, 0); + } + }; + + this.initializing = { + abort: function abort() { + forEach(images, function (image) { + if (!image.complete) { + removeListener(image, EVENT_LOAD, progress); + } + }); + } + }; + forEach(images, function (image) { + if (image.complete) { + progress(); + } else { + addListener(image, EVENT_LOAD, progress, { + once: true + }); + } + }); + } else { + addListener(element, EVENT_CLICK, this.onStart = function (_ref) { + var target = _ref.target; + + if (target.tagName.toLowerCase() === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) { + _this.view(_this.images.indexOf(target)); + } + }); + } + } + }, { + key: "build", + value: function build() { + if (this.ready) { + return; + } + + var element = this.element, + options = this.options; + var parent = element.parentNode; + var template = document.createElement('div'); + template.innerHTML = TEMPLATE; + var viewer = template.querySelector(".".concat(NAMESPACE, "-container")); + var title = viewer.querySelector(".".concat(NAMESPACE, "-title")); + var toolbar = viewer.querySelector(".".concat(NAMESPACE, "-toolbar")); + var navbar = viewer.querySelector(".".concat(NAMESPACE, "-navbar")); + var button = viewer.querySelector(".".concat(NAMESPACE, "-button")); + var canvas = viewer.querySelector(".".concat(NAMESPACE, "-canvas")); + this.parent = parent; + this.viewer = viewer; + this.title = title; + this.toolbar = toolbar; + this.navbar = navbar; + this.button = button; + this.canvas = canvas; + this.footer = viewer.querySelector(".".concat(NAMESPACE, "-footer")); + this.tooltipBox = viewer.querySelector(".".concat(NAMESPACE, "-tooltip")); + this.player = viewer.querySelector(".".concat(NAMESPACE, "-player")); + this.list = viewer.querySelector(".".concat(NAMESPACE, "-list")); + addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title)); + addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar)); + toggleClass(button, CLASS_HIDE, !options.button); + + if (options.backdrop) { + addClass(viewer, "".concat(NAMESPACE, "-backdrop")); + + if (!options.inline && options.backdrop !== 'static') { + setData(canvas, DATA_ACTION, 'hide'); + } + } + + if (isString(options.className) && options.className) { + // In case there are multiple class names + options.className.split(REGEXP_SPACES).forEach(function (className) { + addClass(viewer, className); + }); + } + + if (options.toolbar) { + var list = document.createElement('ul'); + var custom = isPlainObject(options.toolbar); + var zoomButtons = BUTTONS.slice(0, 3); + var rotateButtons = BUTTONS.slice(7, 9); + var scaleButtons = BUTTONS.slice(9); + + if (!custom) { + addClass(toolbar, getResponsiveClass(options.toolbar)); + } + + forEach(custom ? options.toolbar : BUTTONS, function (value, index) { + var deep = custom && isPlainObject(value); + var name = custom ? hyphenate(index) : value; + var show = deep && !isUndefined(value.show) ? value.show : value; + + if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) { + return; + } + + var size = deep && !isUndefined(value.size) ? value.size : value; + var click = deep && !isUndefined(value.click) ? value.click : value; + var item = document.createElement('li'); + item.setAttribute('role', 'button'); + addClass(item, "".concat(NAMESPACE, "-").concat(name)); + + if (!isFunction(click)) { + setData(item, DATA_ACTION, name); + } + + if (isNumber(show)) { + addClass(item, getResponsiveClass(show)); + } + + if (['small', 'large'].indexOf(size) !== -1) { + addClass(item, "".concat(NAMESPACE, "-").concat(size)); + } else if (name === 'play') { + addClass(item, "".concat(NAMESPACE, "-large")); + } + + if (isFunction(click)) { + addListener(item, EVENT_CLICK, click); + } + + list.appendChild(item); + }); + toolbar.appendChild(list); + } else { + addClass(toolbar, CLASS_HIDE); + } + + if (!options.rotatable) { + var rotates = toolbar.querySelectorAll('li[class*="rotate"]'); + addClass(rotates, CLASS_INVISIBLE); + forEach(rotates, function (rotate) { + toolbar.appendChild(rotate); + }); + } + + if (options.inline) { + addClass(button, CLASS_FULLSCREEN); + setStyle(viewer, { + zIndex: options.zIndexInline + }); + + if (window.getComputedStyle(parent).position === 'static') { + setStyle(parent, { + position: 'relative' + }); + } + + parent.insertBefore(viewer, element.nextSibling); + } else { + addClass(button, CLASS_CLOSE); + addClass(viewer, CLASS_FIXED); + addClass(viewer, CLASS_FADE); + addClass(viewer, CLASS_HIDE); + setStyle(viewer, { + zIndex: options.zIndex + }); + var container = options.container; + + if (isString(container)) { + container = element.ownerDocument.querySelector(container); + } + + if (!container) { + container = this.body; + } + + container.appendChild(viewer); + } + + if (options.inline) { + this.render(); + this.bind(); + this.isShown = true; + } + + this.ready = true; + + if (isFunction(options.ready)) { + addListener(element, EVENT_READY, options.ready, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_READY) === false) { + this.ready = false; + return; + } + + if (this.ready && options.inline) { + this.view(this.index); + } + } + /** + * Get the no conflict viewer class. + * @returns {Viewer} The viewer class. + */ + + }], [{ + key: "noConflict", + value: function noConflict() { + window.Viewer = AnotherViewer; + return Viewer; + } + /** + * Change the default options. + * @param {Object} options - The new default options. + */ + + }, { + key: "setDefaults", + value: function setDefaults(options) { + assign(DEFAULTS, isPlainObject(options) && options); + } + }]); + + return Viewer; +}(); + +assign(Viewer.prototype, render, events, handlers, methods, others); + +export default Viewer; diff --git a/src/main/resources/templates/graduation/dist/viewer/js/viewer.js b/src/main/resources/templates/graduation/dist/viewer/js/viewer.js new file mode 100644 index 0000000000000000000000000000000000000000..1712752ff56d94799c62e456d849f06e12db4cf2 --- /dev/null +++ b/src/main/resources/templates/graduation/dist/viewer/js/viewer.js @@ -0,0 +1,3022 @@ +/*! + * Viewer.js v1.3.3 + * https://fengyuanchen.github.io/viewerjs + * + * Copyright 2015-present Chen Fengyuan + * Released under the MIT license + * + * Date: 2019-04-06T14:06:28.301Z + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.Viewer = factory()); +}(this, function () { 'use strict'; + + function _typeof(obj) { + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function (obj) { + return typeof obj; + }; + } else { + _typeof = function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + } + + return _typeof(obj); + } + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + var DEFAULTS = { + /** + * Enable a modal backdrop, specify `static` for a backdrop + * which doesn't close the modal on click. + * @type {boolean} + */ + backdrop: true, + + /** + * Show the button on the top-right of the viewer. + * @type {boolean} + */ + button: true, + + /** + * Show the navbar. + * @type {boolean | number} + */ + navbar: true, + + /** + * Specify the visibility and the content of the title. + * @type {boolean | number | Function | Array} + */ + title: true, + + /** + * Show the toolbar. + * @type {boolean | number | Object} + */ + toolbar: true, + + /** + * Custom class name(s) to add to the viewer's root element. + * @type {string} + */ + className: '', + + /** + * Define where to put the viewer in modal mode. + * @type {string | Element} + */ + container: 'body', + + /** + * Filter the images for viewing. Return true if the image is viewable. + * @type {Function} + */ + filter: null, + + /** + * Enable to request fullscreen when play. + * @type {boolean} + */ + fullscreen: true, + + /** + * Define the initial index of image for viewing. + * @type {number} + */ + initialViewIndex: 0, + + /** + * Enable inline mode. + * @type {boolean} + */ + inline: false, + + /** + * The amount of time to delay between automatically cycling an image when playing. + * @type {number} + */ + interval: 5000, + + /** + * Enable keyboard support. + * @type {boolean} + */ + keyboard: true, + + /** + * Indicate if show a loading spinner when load image or not. + * @type {boolean} + */ + loading: true, + + /** + * Indicate if enable loop viewing or not. + * @type {boolean} + */ + loop: true, + + /** + * Min width of the viewer in inline mode. + * @type {number} + */ + minWidth: 200, + + /** + * Min height of the viewer in inline mode. + * @type {number} + */ + minHeight: 100, + + /** + * Enable to move the image. + * @type {boolean} + */ + movable: true, + + /** + * Enable to zoom the image. + * @type {boolean} + */ + zoomable: true, + + /** + * Enable to rotate the image. + * @type {boolean} + */ + rotatable: true, + + /** + * Enable to scale the image. + * @type {boolean} + */ + scalable: true, + + /** + * Indicate if toggle the image size between its natural size + * and initial size when double click on the image or not. + * @type {boolean} + */ + toggleOnDblclick: true, + + /** + * Show the tooltip with image ratio (percentage) when zoom in or zoom out. + * @type {boolean} + */ + tooltip: true, + + /** + * Enable CSS3 Transition for some special elements. + * @type {boolean} + */ + transition: true, + + /** + * Define the CSS `z-index` value of viewer in modal mode. + * @type {number} + */ + zIndex: 2015, + + /** + * Define the CSS `z-index` value of viewer in inline mode. + * @type {number} + */ + zIndexInline: 0, + + /** + * Define the ratio when zoom the image by wheeling mouse. + * @type {number} + */ + zoomRatio: 0.1, + + /** + * Define the min ratio of the image when zoom out. + * @type {number} + */ + minZoomRatio: 0.01, + + /** + * Define the max ratio of the image when zoom in. + * @type {number} + */ + maxZoomRatio: 100, + + /** + * Define where to get the original image URL for viewing. + * @type {string | Function} + */ + url: 'src', + + /** + * Event shortcuts. + * @type {Function} + */ + ready: null, + show: null, + shown: null, + hide: null, + hidden: null, + view: null, + viewed: null, + zoom: null, + zoomed: null + }; + + var TEMPLATE = '
      ' + '
      ' + '' + '
      ' + '
      ' + '
      ' + '
      '; + + var IS_BROWSER = typeof window !== 'undefined'; + var WINDOW = IS_BROWSER ? window : {}; + var IS_TOUCH_DEVICE = IS_BROWSER ? 'ontouchstart' in WINDOW.document.documentElement : false; + var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false; + var NAMESPACE = 'viewer'; // Actions + + var ACTION_MOVE = 'move'; + var ACTION_SWITCH = 'switch'; + var ACTION_ZOOM = 'zoom'; // Classes + + var CLASS_ACTIVE = "".concat(NAMESPACE, "-active"); + var CLASS_CLOSE = "".concat(NAMESPACE, "-close"); + var CLASS_FADE = "".concat(NAMESPACE, "-fade"); + var CLASS_FIXED = "".concat(NAMESPACE, "-fixed"); + var CLASS_FULLSCREEN = "".concat(NAMESPACE, "-fullscreen"); + var CLASS_FULLSCREEN_EXIT = "".concat(NAMESPACE, "-fullscreen-exit"); + var CLASS_HIDE = "".concat(NAMESPACE, "-hide"); + var CLASS_HIDE_MD_DOWN = "".concat(NAMESPACE, "-hide-md-down"); + var CLASS_HIDE_SM_DOWN = "".concat(NAMESPACE, "-hide-sm-down"); + var CLASS_HIDE_XS_DOWN = "".concat(NAMESPACE, "-hide-xs-down"); + var CLASS_IN = "".concat(NAMESPACE, "-in"); + var CLASS_INVISIBLE = "".concat(NAMESPACE, "-invisible"); + var CLASS_LOADING = "".concat(NAMESPACE, "-loading"); + var CLASS_MOVE = "".concat(NAMESPACE, "-move"); + var CLASS_OPEN = "".concat(NAMESPACE, "-open"); + var CLASS_SHOW = "".concat(NAMESPACE, "-show"); + var CLASS_TRANSITION = "".concat(NAMESPACE, "-transition"); // Events + + var EVENT_CLICK = 'click'; + var EVENT_DBLCLICK = 'dblclick'; + var EVENT_DRAG_START = 'dragstart'; + var EVENT_HIDDEN = 'hidden'; + var EVENT_HIDE = 'hide'; + var EVENT_KEY_DOWN = 'keydown'; + var EVENT_LOAD = 'load'; + var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown'; + var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove'; + var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup'; + var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START; + var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE; + var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END; + var EVENT_READY = 'ready'; + var EVENT_RESIZE = 'resize'; + var EVENT_SHOW = 'show'; + var EVENT_SHOWN = 'shown'; + var EVENT_TRANSITION_END = 'transitionend'; + var EVENT_VIEW = 'view'; + var EVENT_VIEWED = 'viewed'; + var EVENT_WHEEL = 'wheel'; + var EVENT_ZOOM = 'zoom'; + var EVENT_ZOOMED = 'zoomed'; // Data keys + + var DATA_ACTION = "".concat(NAMESPACE, "Action"); // RegExps + + var REGEXP_SPACES = /\s\s*/; // Misc + + var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical']; + + /** + * Check if the given value is a string. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a string, else `false`. + */ + + function isString(value) { + return typeof value === 'string'; + } + /** + * Check if the given value is not a number. + */ + + var isNaN = Number.isNaN || WINDOW.isNaN; + /** + * Check if the given value is a number. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a number, else `false`. + */ + + function isNumber(value) { + return typeof value === 'number' && !isNaN(value); + } + /** + * Check if the given value is undefined. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is undefined, else `false`. + */ + + function isUndefined(value) { + return typeof value === 'undefined'; + } + /** + * Check if the given value is an object. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is an object, else `false`. + */ + + function isObject(value) { + return _typeof(value) === 'object' && value !== null; + } + var hasOwnProperty = Object.prototype.hasOwnProperty; + /** + * Check if the given value is a plain object. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a plain object, else `false`. + */ + + function isPlainObject(value) { + if (!isObject(value)) { + return false; + } + + try { + var _constructor = value.constructor; + var prototype = _constructor.prototype; + return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf'); + } catch (error) { + return false; + } + } + /** + * Check if the given value is a function. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a function, else `false`. + */ + + function isFunction(value) { + return typeof value === 'function'; + } + /** + * Iterate the given data. + * @param {*} data - The data to iterate. + * @param {Function} callback - The process function for each element. + * @returns {*} The original data. + */ + + function forEach(data, callback) { + if (data && isFunction(callback)) { + if (Array.isArray(data) || isNumber(data.length) + /* array-like */ + ) { + var length = data.length; + var i; + + for (i = 0; i < length; i += 1) { + if (callback.call(data, data[i], i, data) === false) { + break; + } + } + } else if (isObject(data)) { + Object.keys(data).forEach(function (key) { + callback.call(data, data[key], key, data); + }); + } + } + + return data; + } + /** + * Extend the given object. + * @param {*} obj - The object to be extended. + * @param {*} args - The rest objects which will be merged to the first object. + * @returns {Object} The extended object. + */ + + var assign = Object.assign || function assign(obj) { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + if (isObject(obj) && args.length > 0) { + args.forEach(function (arg) { + if (isObject(arg)) { + Object.keys(arg).forEach(function (key) { + obj[key] = arg[key]; + }); + } + }); + } + + return obj; + }; + var REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/; + /** + * Apply styles to the given element. + * @param {Element} element - The target element. + * @param {Object} styles - The styles for applying. + */ + + function setStyle(element, styles) { + var style = element.style; + forEach(styles, function (value, property) { + if (REGEXP_SUFFIX.test(property) && isNumber(value)) { + value += 'px'; + } + + style[property] = value; + }); + } + /** + * Check if the given element has a special class. + * @param {Element} element - The element to check. + * @param {string} value - The class to search. + * @returns {boolean} Returns `true` if the special class was found. + */ + + function hasClass(element, value) { + return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1; + } + /** + * Add classes to the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be added. + */ + + function addClass(element, value) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + addClass(elem, value); + }); + return; + } + + if (element.classList) { + element.classList.add(value); + return; + } + + var className = element.className.trim(); + + if (!className) { + element.className = value; + } else if (className.indexOf(value) < 0) { + element.className = "".concat(className, " ").concat(value); + } + } + /** + * Remove classes from the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be removed. + */ + + function removeClass(element, value) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + removeClass(elem, value); + }); + return; + } + + if (element.classList) { + element.classList.remove(value); + return; + } + + if (element.className.indexOf(value) >= 0) { + element.className = element.className.replace(value, ''); + } + } + /** + * Add or remove classes from the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be toggled. + * @param {boolean} added - Add only. + */ + + function toggleClass(element, value, added) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + toggleClass(elem, value, added); + }); + return; + } // IE10-11 doesn't support the second parameter of `classList.toggle` + + + if (added) { + addClass(element, value); + } else { + removeClass(element, value); + } + } + var REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g; + /** + * Transform the given string from camelCase to kebab-case + * @param {string} value - The value to transform. + * @returns {string} The transformed value. + */ + + function hyphenate(value) { + return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase(); + } + /** + * Get data from the given element. + * @param {Element} element - The target element. + * @param {string} name - The data key to get. + * @returns {string} The data value. + */ + + function getData(element, name) { + if (isObject(element[name])) { + return element[name]; + } + + if (element.dataset) { + return element.dataset[name]; + } + + return element.getAttribute("data-".concat(hyphenate(name))); + } + /** + * Set data to the given element. + * @param {Element} element - The target element. + * @param {string} name - The data key to set. + * @param {string} data - The data value. + */ + + function setData(element, name, data) { + if (isObject(data)) { + element[name] = data; + } else if (element.dataset) { + element.dataset[name] = data; + } else { + element.setAttribute("data-".concat(hyphenate(name)), data); + } + } + + var onceSupported = function () { + var supported = false; + + if (IS_BROWSER) { + var once = false; + + var listener = function listener() {}; + + var options = Object.defineProperty({}, 'once', { + get: function get() { + supported = true; + return once; + }, + + /** + * This setter can fix a `TypeError` in strict mode + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only} + * @param {boolean} value - The value to set + */ + set: function set(value) { + once = value; + } + }); + WINDOW.addEventListener('test', listener, options); + WINDOW.removeEventListener('test', listener, options); + } + + return supported; + }(); + /** + * Remove event listener from the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Function} listener - The event listener. + * @param {Object} options - The event options. + */ + + + function removeListener(element, type, listener) { + var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + var handler = listener; + type.trim().split(REGEXP_SPACES).forEach(function (event) { + if (!onceSupported) { + var listeners = element.listeners; + + if (listeners && listeners[event] && listeners[event][listener]) { + handler = listeners[event][listener]; + delete listeners[event][listener]; + + if (Object.keys(listeners[event]).length === 0) { + delete listeners[event]; + } + + if (Object.keys(listeners).length === 0) { + delete element.listeners; + } + } + } + + element.removeEventListener(event, handler, options); + }); + } + /** + * Add event listener to the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Function} listener - The event listener. + * @param {Object} options - The event options. + */ + + function addListener(element, type, listener) { + var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + var _handler = listener; + type.trim().split(REGEXP_SPACES).forEach(function (event) { + if (options.once && !onceSupported) { + var _element$listeners = element.listeners, + listeners = _element$listeners === void 0 ? {} : _element$listeners; + + _handler = function handler() { + delete listeners[event][listener]; + element.removeEventListener(event, _handler, options); + + for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + listener.apply(element, args); + }; + + if (!listeners[event]) { + listeners[event] = {}; + } + + if (listeners[event][listener]) { + element.removeEventListener(event, listeners[event][listener], options); + } + + listeners[event][listener] = _handler; + element.listeners = listeners; + } + + element.addEventListener(event, _handler, options); + }); + } + /** + * Dispatch event on the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Object} data - The additional event data. + * @returns {boolean} Indicate if the event is default prevented or not. + */ + + function dispatchEvent(element, type, data) { + var event; // Event and CustomEvent on IE9-11 are global objects, not constructors + + if (isFunction(Event) && isFunction(CustomEvent)) { + event = new CustomEvent(type, { + detail: data, + bubbles: true, + cancelable: true + }); + } else { + event = document.createEvent('CustomEvent'); + event.initCustomEvent(type, true, true, data); + } + + return element.dispatchEvent(event); + } + /** + * Get the offset base on the document. + * @param {Element} element - The target element. + * @returns {Object} The offset data. + */ + + function getOffset(element) { + var box = element.getBoundingClientRect(); + return { + left: box.left + (window.pageXOffset - document.documentElement.clientLeft), + top: box.top + (window.pageYOffset - document.documentElement.clientTop) + }; + } + /** + * Get transforms base on the given object. + * @param {Object} obj - The target object. + * @returns {string} A string contains transform values. + */ + + function getTransforms(_ref) { + var rotate = _ref.rotate, + scaleX = _ref.scaleX, + scaleY = _ref.scaleY, + translateX = _ref.translateX, + translateY = _ref.translateY; + var values = []; + + if (isNumber(translateX) && translateX !== 0) { + values.push("translateX(".concat(translateX, "px)")); + } + + if (isNumber(translateY) && translateY !== 0) { + values.push("translateY(".concat(translateY, "px)")); + } // Rotate should come first before scale to match orientation transform + + + if (isNumber(rotate) && rotate !== 0) { + values.push("rotate(".concat(rotate, "deg)")); + } + + if (isNumber(scaleX) && scaleX !== 1) { + values.push("scaleX(".concat(scaleX, ")")); + } + + if (isNumber(scaleY) && scaleY !== 1) { + values.push("scaleY(".concat(scaleY, ")")); + } + + var transform = values.length ? values.join(' ') : 'none'; + return { + WebkitTransform: transform, + msTransform: transform, + transform: transform + }; + } + /** + * Get an image name from an image url. + * @param {string} url - The target url. + * @example + * // picture.jpg + * getImageNameFromURL('http://domain.com/path/to/picture.jpg?size=1280×960') + * @returns {string} A string contains the image name. + */ + + function getImageNameFromURL(url) { + return isString(url) ? url.replace(/^.*\//, '').replace(/[?&#].*$/, '') : ''; + } + var IS_SAFARI = WINDOW.navigator && /(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(WINDOW.navigator.userAgent); + /** + * Get an image's natural sizes. + * @param {string} image - The target image. + * @param {Function} callback - The callback function. + * @returns {HTMLImageElement} The new image. + */ + + function getImageNaturalSizes(image, callback) { + var newImage = document.createElement('img'); // Modern browsers (except Safari) + + if (image.naturalWidth && !IS_SAFARI) { + callback(image.naturalWidth, image.naturalHeight); + return newImage; + } + + var body = document.body || document.documentElement; + + newImage.onload = function () { + callback(newImage.width, newImage.height); + + if (!IS_SAFARI) { + body.removeChild(newImage); + } + }; + + newImage.src = image.src; // iOS Safari will convert the image automatically + // with its orientation once append it into DOM + + if (!IS_SAFARI) { + newImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;'; + body.appendChild(newImage); + } + + return newImage; + } + /** + * Get the related class name of a responsive type number. + * @param {string} type - The responsive type. + * @returns {string} The related class name. + */ + + function getResponsiveClass(type) { + switch (type) { + case 2: + return CLASS_HIDE_XS_DOWN; + + case 3: + return CLASS_HIDE_SM_DOWN; + + case 4: + return CLASS_HIDE_MD_DOWN; + + default: + return ''; + } + } + /** + * Get the max ratio of a group of pointers. + * @param {string} pointers - The target pointers. + * @returns {number} The result ratio. + */ + + function getMaxZoomRatio(pointers) { + var pointers2 = assign({}, pointers); + var ratios = []; + forEach(pointers, function (pointer, pointerId) { + delete pointers2[pointerId]; + forEach(pointers2, function (pointer2) { + var x1 = Math.abs(pointer.startX - pointer2.startX); + var y1 = Math.abs(pointer.startY - pointer2.startY); + var x2 = Math.abs(pointer.endX - pointer2.endX); + var y2 = Math.abs(pointer.endY - pointer2.endY); + var z1 = Math.sqrt(x1 * x1 + y1 * y1); + var z2 = Math.sqrt(x2 * x2 + y2 * y2); + var ratio = (z2 - z1) / z1; + ratios.push(ratio); + }); + }); + ratios.sort(function (a, b) { + return Math.abs(a) < Math.abs(b); + }); + return ratios[0]; + } + /** + * Get a pointer from an event object. + * @param {Object} event - The target event object. + * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not. + * @returns {Object} The result pointer contains start and/or end point coordinates. + */ + + function getPointer(_ref2, endOnly) { + var pageX = _ref2.pageX, + pageY = _ref2.pageY; + var end = { + endX: pageX, + endY: pageY + }; + return endOnly ? end : assign({ + timeStamp: Date.now(), + startX: pageX, + startY: pageY + }, end); + } + /** + * Get the center point coordinate of a group of pointers. + * @param {Object} pointers - The target pointers. + * @returns {Object} The center point coordinate. + */ + + function getPointersCenter(pointers) { + var pageX = 0; + var pageY = 0; + var count = 0; + forEach(pointers, function (_ref3) { + var startX = _ref3.startX, + startY = _ref3.startY; + pageX += startX; + pageY += startY; + count += 1; + }); + pageX /= count; + pageY /= count; + return { + pageX: pageX, + pageY: pageY + }; + } + + var render = { + render: function render() { + this.initContainer(); + this.initViewer(); + this.initList(); + this.renderViewer(); + }, + initContainer: function initContainer() { + this.containerData = { + width: window.innerWidth, + height: window.innerHeight + }; + }, + initViewer: function initViewer() { + var options = this.options, + parent = this.parent; + var viewerData; + + if (options.inline) { + viewerData = { + width: Math.max(parent.offsetWidth, options.minWidth), + height: Math.max(parent.offsetHeight, options.minHeight) + }; + this.parentData = viewerData; + } + + if (this.fulled || !viewerData) { + viewerData = this.containerData; + } + + this.viewerData = assign({}, viewerData); + }, + renderViewer: function renderViewer() { + if (this.options.inline && !this.fulled) { + setStyle(this.viewer, this.viewerData); + } + }, + initList: function initList() { + var _this = this; + + var element = this.element, + options = this.options, + list = this.list; + var items = []; + forEach(this.images, function (image, i) { + var src = image.src; + var alt = image.alt || getImageNameFromURL(src); + var url = options.url; + + if (isString(url)) { + url = image.getAttribute(url); + } else if (isFunction(url)) { + url = url.call(_this, image); + } + + if (src || url) { + items.push('
    • ' + '' + '
    • '); + } + }); + list.innerHTML = items.join(''); + this.items = list.getElementsByTagName('li'); + forEach(this.items, function (item) { + var image = item.firstElementChild; + setData(image, 'filled', true); + + if (options.loading) { + addClass(item, CLASS_LOADING); + } + + addListener(image, EVENT_LOAD, function (event) { + if (options.loading) { + removeClass(item, CLASS_LOADING); + } + + _this.loadImage(event); + }, { + once: true + }); + }); + + if (options.transition) { + addListener(element, EVENT_VIEWED, function () { + addClass(list, CLASS_TRANSITION); + }, { + once: true + }); + } + }, + renderList: function renderList(index) { + var i = index || this.index; + var width = this.items[i].offsetWidth || 30; + var outerWidth = width + 1; // 1 pixel of `margin-left` width + // Place the active item in the center of the screen + + setStyle(this.list, assign({ + width: outerWidth * this.length + }, getTransforms({ + translateX: (this.viewerData.width - width) / 2 - outerWidth * i + }))); + }, + resetList: function resetList() { + var list = this.list; + list.innerHTML = ''; + removeClass(list, CLASS_TRANSITION); + setStyle(list, getTransforms({ + translateX: 0 + })); + }, + initImage: function initImage(done) { + var _this2 = this; + + var options = this.options, + image = this.image, + viewerData = this.viewerData; + var footerHeight = this.footer.offsetHeight; + var viewerWidth = viewerData.width; + var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight); + var oldImageData = this.imageData || {}; + var sizingImage; + this.imageInitializing = { + abort: function abort() { + sizingImage.onload = null; + } + }; + sizingImage = getImageNaturalSizes(image, function (naturalWidth, naturalHeight) { + var aspectRatio = naturalWidth / naturalHeight; + var width = viewerWidth; + var height = viewerHeight; + _this2.imageInitializing = false; + + if (viewerHeight * aspectRatio > viewerWidth) { + height = viewerWidth / aspectRatio; + } else { + width = viewerHeight * aspectRatio; + } + + width = Math.min(width * 0.9, naturalWidth); + height = Math.min(height * 0.9, naturalHeight); + var imageData = { + naturalWidth: naturalWidth, + naturalHeight: naturalHeight, + aspectRatio: aspectRatio, + ratio: width / naturalWidth, + width: width, + height: height, + left: (viewerWidth - width) / 2, + top: (viewerHeight - height) / 2 + }; + var initialImageData = assign({}, imageData); + + if (options.rotatable) { + imageData.rotate = oldImageData.rotate || 0; + initialImageData.rotate = 0; + } + + if (options.scalable) { + imageData.scaleX = oldImageData.scaleX || 1; + imageData.scaleY = oldImageData.scaleY || 1; + initialImageData.scaleX = 1; + initialImageData.scaleY = 1; + } + + _this2.imageData = imageData; + _this2.initialImageData = initialImageData; + + if (done) { + done(); + } + }); + }, + renderImage: function renderImage(done) { + var _this3 = this; + + var image = this.image, + imageData = this.imageData; + setStyle(image, assign({ + width: imageData.width, + height: imageData.height, + // XXX: Not to use translateX/Y to avoid image shaking when zooming + marginLeft: imageData.left, + marginTop: imageData.top + }, getTransforms(imageData))); + + if (done) { + if ((this.viewing || this.zooming) && this.options.transition) { + var onTransitionEnd = function onTransitionEnd() { + _this3.imageRendering = false; + done(); + }; + + this.imageRendering = { + abort: function abort() { + removeListener(image, EVENT_TRANSITION_END, onTransitionEnd); + } + }; + addListener(image, EVENT_TRANSITION_END, onTransitionEnd, { + once: true + }); + } else { + done(); + } + } + }, + resetImage: function resetImage() { + // this.image only defined after viewed + if (this.viewing || this.viewed) { + var image = this.image; + + if (this.viewing) { + this.viewing.abort(); + } + + image.parentNode.removeChild(image); + this.image = null; + } + } + }; + + var events = { + bind: function bind() { + var options = this.options, + viewer = this.viewer, + canvas = this.canvas; + var document = this.element.ownerDocument; + addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this)); + addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), { + passive: false, + capture: true + }); + addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this)); + addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this)); + addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this)); + addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this)); + addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this)); + addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this)); + + if (options.toggleOnDblclick) { + addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this)); + } + }, + unbind: function unbind() { + var options = this.options, + viewer = this.viewer, + canvas = this.canvas; + var document = this.element.ownerDocument; + removeListener(viewer, EVENT_CLICK, this.onClick); + removeListener(viewer, EVENT_WHEEL, this.onWheel, { + passive: false, + capture: true + }); + removeListener(viewer, EVENT_DRAG_START, this.onDragStart); + removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown); + removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove); + removeListener(document, EVENT_POINTER_UP, this.onPointerUp); + removeListener(document, EVENT_KEY_DOWN, this.onKeyDown); + removeListener(window, EVENT_RESIZE, this.onResize); + + if (options.toggleOnDblclick) { + removeListener(canvas, EVENT_DBLCLICK, this.onDblclick); + } + } + }; + + var handlers = { + click: function click(event) { + var target = event.target; + var options = this.options, + imageData = this.imageData; + var action = getData(target, DATA_ACTION); // Cancel the emulated click when the native click event was triggered. + + if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) { + clearTimeout(this.clickCanvasTimeout); + } + + switch (action) { + case 'mix': + if (this.played) { + this.stop(); + } else if (options.inline) { + if (this.fulled) { + this.exit(); + } else { + this.full(); + } + } else { + this.hide(); + } + + break; + + case 'hide': + this.hide(); + break; + + case 'view': + this.view(getData(target, 'index')); + break; + + case 'zoom-in': + this.zoom(0.1, true); + break; + + case 'zoom-out': + this.zoom(-0.1, true); + break; + + case 'one-to-one': + this.toggle(); + break; + + case 'reset': + this.reset(); + break; + + case 'prev': + this.prev(options.loop); + break; + + case 'play': + this.play(options.fullscreen); + break; + + case 'next': + this.next(options.loop); + break; + + case 'rotate-left': + this.rotate(-90); + break; + + case 'rotate-right': + this.rotate(90); + break; + + case 'flip-horizontal': + this.scaleX(-imageData.scaleX || -1); + break; + + case 'flip-vertical': + this.scaleY(-imageData.scaleY || -1); + break; + + default: + if (this.played) { + this.stop(); + } + + } + }, + dblclick: function dblclick(event) { + event.preventDefault(); + + if (this.viewed && event.target === this.image) { + // Cancel the emulated double click when the native dblclick event was triggered. + if (IS_TOUCH_DEVICE && event.isTrusted) { + clearTimeout(this.doubleClickImageTimeout); + } + + this.toggle(); + } + }, + load: function load() { + var _this = this; + + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = false; + } + + var element = this.element, + options = this.options, + image = this.image, + index = this.index, + viewerData = this.viewerData; + removeClass(image, CLASS_INVISIBLE); + + if (options.loading) { + removeClass(this.canvas, CLASS_LOADING); + } + + image.style.cssText = 'height:0;' + "margin-left:".concat(viewerData.width / 2, "px;") + "margin-top:".concat(viewerData.height / 2, "px;") + 'max-width:none!important;' + 'position:absolute;' + 'width:0;'; + this.initImage(function () { + toggleClass(image, CLASS_MOVE, options.movable); + toggleClass(image, CLASS_TRANSITION, options.transition); + + _this.renderImage(function () { + _this.viewed = true; + _this.viewing = false; + + if (isFunction(options.viewed)) { + addListener(element, EVENT_VIEWED, options.viewed, { + once: true + }); + } + + dispatchEvent(element, EVENT_VIEWED, { + originalImage: _this.images[index], + index: index, + image: image + }); + }); + }); + }, + loadImage: function loadImage(event) { + var image = event.target; + var parent = image.parentNode; + var parentWidth = parent.offsetWidth || 30; + var parentHeight = parent.offsetHeight || 50; + var filled = !!getData(image, 'filled'); + getImageNaturalSizes(image, function (naturalWidth, naturalHeight) { + var aspectRatio = naturalWidth / naturalHeight; + var width = parentWidth; + var height = parentHeight; + + if (parentHeight * aspectRatio > parentWidth) { + if (filled) { + width = parentHeight * aspectRatio; + } else { + height = parentWidth / aspectRatio; + } + } else if (filled) { + height = parentWidth / aspectRatio; + } else { + width = parentHeight * aspectRatio; + } + + setStyle(image, assign({ + width: width, + height: height + }, getTransforms({ + translateX: (parentWidth - width) / 2, + translateY: (parentHeight - height) / 2 + }))); + }); + }, + keydown: function keydown(event) { + var options = this.options; + + if (!this.fulled || !options.keyboard) { + return; + } + + switch (event.keyCode || event.which || event.charCode) { + // Escape + case 27: + if (this.played) { + this.stop(); + } else if (options.inline) { + if (this.fulled) { + this.exit(); + } + } else { + this.hide(); + } + + break; + // Space + + case 32: + if (this.played) { + this.stop(); + } + + break; + // ArrowLeft + + case 37: + this.prev(options.loop); + break; + // ArrowUp + + case 38: + // Prevent scroll on Firefox + event.preventDefault(); // Zoom in + + this.zoom(options.zoomRatio, true); + break; + // ArrowRight + + case 39: + this.next(options.loop); + break; + // ArrowDown + + case 40: + // Prevent scroll on Firefox + event.preventDefault(); // Zoom out + + this.zoom(-options.zoomRatio, true); + break; + // Ctrl + 0 + + case 48: // Fall through + // Ctrl + 1 + // eslint-disable-next-line no-fallthrough + + case 49: + if (event.ctrlKey) { + event.preventDefault(); + this.toggle(); + } + + break; + + default: + } + }, + dragstart: function dragstart(event) { + if (event.target.tagName.toLowerCase() === 'img') { + event.preventDefault(); + } + }, + pointerdown: function pointerdown(event) { + var options = this.options, + pointers = this.pointers; + var buttons = event.buttons, + button = event.button; + + if (!this.viewed || this.showing || this.viewing || this.hiding // No primary button (Usually the left button) + // Note that touch events have no `buttons` or `button` property + || isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0 // Open context menu + || event.ctrlKey) { + return; + } // Prevent default behaviours as page zooming in touch devices. + + + event.preventDefault(); + + if (event.changedTouches) { + forEach(event.changedTouches, function (touch) { + pointers[touch.identifier] = getPointer(touch); + }); + } else { + pointers[event.pointerId || 0] = getPointer(event); + } + + var action = options.movable ? ACTION_MOVE : false; + + if (Object.keys(pointers).length > 1) { + action = ACTION_ZOOM; + } else if ((event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) { + action = ACTION_SWITCH; + } + + if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) { + removeClass(this.image, CLASS_TRANSITION); + } + + this.action = action; + }, + pointermove: function pointermove(event) { + var pointers = this.pointers, + action = this.action; + + if (!this.viewed || !action) { + return; + } + + event.preventDefault(); + + if (event.changedTouches) { + forEach(event.changedTouches, function (touch) { + assign(pointers[touch.identifier] || {}, getPointer(touch, true)); + }); + } else { + assign(pointers[event.pointerId || 0] || {}, getPointer(event, true)); + } + + this.change(event); + }, + pointerup: function pointerup(event) { + var _this2 = this; + + var options = this.options, + action = this.action, + pointers = this.pointers; + var pointer; + + if (event.changedTouches) { + forEach(event.changedTouches, function (touch) { + pointer = pointers[touch.identifier]; + delete pointers[touch.identifier]; + }); + } else { + pointer = pointers[event.pointerId || 0]; + delete pointers[event.pointerId || 0]; + } + + if (!action) { + return; + } + + event.preventDefault(); + + if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) { + addClass(this.image, CLASS_TRANSITION); + } + + this.action = false; // Emulate click and double click in touch devices to support backdrop and image zooming (#210). + + if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) { + clearTimeout(this.clickCanvasTimeout); + clearTimeout(this.doubleClickImageTimeout); + + if (options.toggleOnDblclick && this.viewed && event.target === this.image) { + if (this.imageClicked) { + this.imageClicked = false; // This timeout will be cleared later when a native dblclick event is triggering + + this.doubleClickImageTimeout = setTimeout(function () { + dispatchEvent(_this2.image, EVENT_DBLCLICK); + }, 50); + } else { + this.imageClicked = true; // The default timing of a double click in Windows is 500 ms + + this.doubleClickImageTimeout = setTimeout(function () { + _this2.imageClicked = false; + }, 500); + } + } else { + this.imageClicked = false; + + if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) { + // This timeout will be cleared later when a native click event is triggering + this.clickCanvasTimeout = setTimeout(function () { + dispatchEvent(_this2.canvas, EVENT_CLICK); + }, 50); + } + } + } + }, + resize: function resize() { + var _this3 = this; + + if (!this.isShown || this.hiding) { + return; + } + + this.initContainer(); + this.initViewer(); + this.renderViewer(); + this.renderList(); + + if (this.viewed) { + this.initImage(function () { + _this3.renderImage(); + }); + } + + if (this.played) { + if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { + this.stop(); + return; + } + + forEach(this.player.getElementsByTagName('img'), function (image) { + addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), { + once: true + }); + dispatchEvent(image, EVENT_LOAD); + }); + } + }, + wheel: function wheel(event) { + var _this4 = this; + + if (!this.viewed) { + return; + } + + event.preventDefault(); // Limit wheel speed to prevent zoom too fast + + if (this.wheeling) { + return; + } + + this.wheeling = true; + setTimeout(function () { + _this4.wheeling = false; + }, 50); + var ratio = Number(this.options.zoomRatio) || 0.1; + var delta = 1; + + if (event.deltaY) { + delta = event.deltaY > 0 ? 1 : -1; + } else if (event.wheelDelta) { + delta = -event.wheelDelta / 120; + } else if (event.detail) { + delta = event.detail > 0 ? 1 : -1; + } + + this.zoom(-delta * ratio, true, event); + } + }; + + var methods = { + /** Show the viewer (only available in modal mode) + * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not. + * @returns {Viewer} this + */ + show: function show() { + var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var element = this.element, + options = this.options; + + if (options.inline || this.showing || this.isShown || this.showing) { + return this; + } + + if (!this.ready) { + this.build(); + + if (this.ready) { + this.show(immediate); + } + + return this; + } + + if (isFunction(options.show)) { + addListener(element, EVENT_SHOW, options.show, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) { + return this; + } + + if (this.hiding) { + this.transitioning.abort(); + } + + this.showing = true; + this.open(); + var viewer = this.viewer; + removeClass(viewer, CLASS_HIDE); + + if (options.transition && !immediate) { + var shown = this.shown.bind(this); + this.transitioning = { + abort: function abort() { + removeListener(viewer, EVENT_TRANSITION_END, shown); + removeClass(viewer, CLASS_IN); + } + }; + addClass(viewer, CLASS_TRANSITION); // Force reflow to enable CSS3 transition + // eslint-disable-next-line + + viewer.offsetWidth; + addListener(viewer, EVENT_TRANSITION_END, shown, { + once: true + }); + addClass(viewer, CLASS_IN); + } else { + addClass(viewer, CLASS_IN); + this.shown(); + } + + return this; + }, + + /** + * Hide the viewer (only available in modal mode) + * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not. + * @returns {Viewer} this + */ + hide: function hide() { + var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var element = this.element, + options = this.options; + + if (options.inline || this.hiding || !(this.isShown || this.showing)) { + return this; + } + + if (isFunction(options.hide)) { + addListener(element, EVENT_HIDE, options.hide, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_HIDE) === false) { + return this; + } + + if (this.showing) { + this.transitioning.abort(); + } + + this.hiding = true; + + if (this.played) { + this.stop(); + } else if (this.viewing) { + this.viewing.abort(); + } + + var viewer = this.viewer; + + if (options.transition && !immediate) { + var hidden = this.hidden.bind(this); + + var hide = function hide() { + addListener(viewer, EVENT_TRANSITION_END, hidden, { + once: true + }); + removeClass(viewer, CLASS_IN); + }; + + this.transitioning = { + abort: function abort() { + if (this.viewed) { + removeListener(this.image, EVENT_TRANSITION_END, hide); + } else { + removeListener(viewer, EVENT_TRANSITION_END, hidden); + } + } + }; // Note that the `CLASS_TRANSITION` class will be removed on pointer down (#255) + + if (this.viewed && hasClass(this.image, CLASS_TRANSITION)) { + addListener(this.image, EVENT_TRANSITION_END, hide, { + once: true + }); + this.zoomTo(0, false, false, true); + } else { + hide(); + } + } else { + removeClass(viewer, CLASS_IN); + this.hidden(); + } + + return this; + }, + + /** + * View one of the images with image's index + * @param {number} index - The index of the image to view. + * @returns {Viewer} this + */ + view: function view() { + var _this = this; + + var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex; + index = Number(index) || 0; + + if (!this.isShown) { + this.index = index; + return this.show(); + } + + if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) { + return this; + } + + if (this.viewing) { + this.viewing.abort(); + } + + var element = this.element, + options = this.options, + title = this.title, + canvas = this.canvas; + var item = this.items[index]; + var img = item.querySelector('img'); + var url = getData(img, 'originalUrl'); + var alt = img.getAttribute('alt'); + var image = document.createElement('img'); + image.src = url; + image.alt = alt; + + if (isFunction(options.view)) { + addListener(element, EVENT_VIEW, options.view, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_VIEW, { + originalImage: this.images[index], + index: index, + image: image + }) === false || !this.isShown || this.hiding || this.played) { + return this; + } + + this.image = image; + removeClass(this.items[this.index], CLASS_ACTIVE); + addClass(item, CLASS_ACTIVE); + this.viewed = false; + this.index = index; + this.imageData = {}; + addClass(image, CLASS_INVISIBLE); + + if (options.loading) { + addClass(canvas, CLASS_LOADING); + } + + canvas.innerHTML = ''; + canvas.appendChild(image); // Center current item + + this.renderList(); // Clear title + + title.innerHTML = ''; // Generate title after viewed + + var onViewed = function onViewed() { + var imageData = _this.imageData; + var render = Array.isArray(options.title) ? options.title[1] : options.title; + title.innerHTML = isFunction(render) ? render.call(_this, image, imageData) : "".concat(alt, " (").concat(imageData.naturalWidth, " \xD7 ").concat(imageData.naturalHeight, ")"); + }; + + var onLoad; + addListener(element, EVENT_VIEWED, onViewed, { + once: true + }); + this.viewing = { + abort: function abort() { + removeListener(element, EVENT_VIEWED, onViewed); + + if (image.complete) { + if (this.imageRendering) { + this.imageRendering.abort(); + } else if (this.imageInitializing) { + this.imageInitializing.abort(); + } + } else { + // Cancel download to save bandwidth. + image.src = ''; + removeListener(image, EVENT_LOAD, onLoad); + + if (this.timeout) { + clearTimeout(this.timeout); + } + } + } + }; + + if (image.complete) { + this.load(); + } else { + addListener(image, EVENT_LOAD, onLoad = this.load.bind(this), { + once: true + }); + + if (this.timeout) { + clearTimeout(this.timeout); + } // Make the image visible if it fails to load within 1s + + + this.timeout = setTimeout(function () { + removeClass(image, CLASS_INVISIBLE); + _this.timeout = false; + }, 1000); + } + + return this; + }, + + /** + * View the previous image + * @param {boolean} [loop=false] - Indicate if view the last one + * when it is the first one at present. + * @returns {Viewer} this + */ + prev: function prev() { + var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var index = this.index - 1; + + if (index < 0) { + index = loop ? this.length - 1 : 0; + } + + this.view(index); + return this; + }, + + /** + * View the next image + * @param {boolean} [loop=false] - Indicate if view the first one + * when it is the last one at present. + * @returns {Viewer} this + */ + next: function next() { + var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var maxIndex = this.length - 1; + var index = this.index + 1; + + if (index > maxIndex) { + index = loop ? 0 : maxIndex; + } + + this.view(index); + return this; + }, + + /** + * Move the image with relative offsets. + * @param {number} offsetX - The relative offset distance on the x-axis. + * @param {number} offsetY - The relative offset distance on the y-axis. + * @returns {Viewer} this + */ + move: function move(offsetX, offsetY) { + var imageData = this.imageData; + this.moveTo(isUndefined(offsetX) ? offsetX : imageData.left + Number(offsetX), isUndefined(offsetY) ? offsetY : imageData.top + Number(offsetY)); + return this; + }, + + /** + * Move the image to an absolute point. + * @param {number} x - The x-axis coordinate. + * @param {number} [y=x] - The y-axis coordinate. + * @returns {Viewer} this + */ + moveTo: function moveTo(x) { + var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x; + var imageData = this.imageData; + x = Number(x); + y = Number(y); + + if (this.viewed && !this.played && this.options.movable) { + var changed = false; + + if (isNumber(x)) { + imageData.left = x; + changed = true; + } + + if (isNumber(y)) { + imageData.top = y; + changed = true; + } + + if (changed) { + this.renderImage(); + } + } + + return this; + }, + + /** + * Zoom the image with a relative ratio. + * @param {number} ratio - The target ratio. + * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not. + * @param {Event} [_originalEvent=null] - The original event if any. + * @returns {Viewer} this + */ + zoom: function zoom(ratio) { + var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + + var imageData = this.imageData; + ratio = Number(ratio); + + if (ratio < 0) { + ratio = 1 / (1 - ratio); + } else { + ratio = 1 + ratio; + } + + this.zoomTo(imageData.width * ratio / imageData.naturalWidth, hasTooltip, _originalEvent); + return this; + }, + + /** + * Zoom the image to an absolute ratio. + * @param {number} ratio - The target ratio. + * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not. + * @param {Event} [_originalEvent=null] - The original event if any. + * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not. + * @returns {Viewer} this + */ + zoomTo: function zoomTo(ratio) { + var _this2 = this; + + var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + + var _zoomable = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + + var element = this.element, + options = this.options, + pointers = this.pointers, + imageData = this.imageData; + var width = imageData.width, + height = imageData.height, + left = imageData.left, + top = imageData.top, + naturalWidth = imageData.naturalWidth, + naturalHeight = imageData.naturalHeight; + ratio = Math.max(0, ratio); + + if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) { + if (!_zoomable) { + var minZoomRatio = Math.max(0.01, options.minZoomRatio); + var maxZoomRatio = Math.min(100, options.maxZoomRatio); + ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio); + } + + if (_originalEvent && ratio > 0.95 && ratio < 1.05) { + ratio = 1; + } + + var newWidth = naturalWidth * ratio; + var newHeight = naturalHeight * ratio; + var offsetWidth = newWidth - width; + var offsetHeight = newHeight - height; + var oldRatio = width / naturalWidth; + + if (isFunction(options.zoom)) { + addListener(element, EVENT_ZOOM, options.zoom, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_ZOOM, { + ratio: ratio, + oldRatio: oldRatio, + originalEvent: _originalEvent + }) === false) { + return this; + } + + this.zooming = true; + + if (_originalEvent) { + var offset = getOffset(this.viewer); + var center = pointers && Object.keys(pointers).length ? getPointersCenter(pointers) : { + pageX: _originalEvent.pageX, + pageY: _originalEvent.pageY + }; // Zoom from the triggering point of the event + + imageData.left -= offsetWidth * ((center.pageX - offset.left - left) / width); + imageData.top -= offsetHeight * ((center.pageY - offset.top - top) / height); + } else { + // Zoom from the center of the image + imageData.left -= offsetWidth / 2; + imageData.top -= offsetHeight / 2; + } + + imageData.width = newWidth; + imageData.height = newHeight; + imageData.ratio = ratio; + this.renderImage(function () { + _this2.zooming = false; + + if (isFunction(options.zoomed)) { + addListener(element, EVENT_ZOOMED, options.zoomed, { + once: true + }); + } + + dispatchEvent(element, EVENT_ZOOMED, { + ratio: ratio, + oldRatio: oldRatio, + originalEvent: _originalEvent + }); + }); + + if (hasTooltip) { + this.tooltip(); + } + } + + return this; + }, + + /** + * Rotate the image with a relative degree. + * @param {number} degree - The rotate degree. + * @returns {Viewer} this + */ + rotate: function rotate(degree) { + this.rotateTo((this.imageData.rotate || 0) + Number(degree)); + return this; + }, + + /** + * Rotate the image to an absolute degree. + * @param {number} degree - The rotate degree. + * @returns {Viewer} this + */ + rotateTo: function rotateTo(degree) { + var imageData = this.imageData; + degree = Number(degree); + + if (isNumber(degree) && this.viewed && !this.played && this.options.rotatable) { + imageData.rotate = degree; + this.renderImage(); + } + + return this; + }, + + /** + * Scale the image on the x-axis. + * @param {number} scaleX - The scale ratio on the x-axis. + * @returns {Viewer} this + */ + scaleX: function scaleX(_scaleX) { + this.scale(_scaleX, this.imageData.scaleY); + return this; + }, + + /** + * Scale the image on the y-axis. + * @param {number} scaleY - The scale ratio on the y-axis. + * @returns {Viewer} this + */ + scaleY: function scaleY(_scaleY) { + this.scale(this.imageData.scaleX, _scaleY); + return this; + }, + + /** + * Scale the image. + * @param {number} scaleX - The scale ratio on the x-axis. + * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis. + * @returns {Viewer} this + */ + scale: function scale(scaleX) { + var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX; + var imageData = this.imageData; + scaleX = Number(scaleX); + scaleY = Number(scaleY); + + if (this.viewed && !this.played && this.options.scalable) { + var changed = false; + + if (isNumber(scaleX)) { + imageData.scaleX = scaleX; + changed = true; + } + + if (isNumber(scaleY)) { + imageData.scaleY = scaleY; + changed = true; + } + + if (changed) { + this.renderImage(); + } + } + + return this; + }, + + /** + * Play the images + * @param {boolean} [fullscreen=false] - Indicate if request fullscreen or not. + * @returns {Viewer} this + */ + play: function play() { + var _this3 = this; + + var fullscreen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + + if (!this.isShown || this.played) { + return this; + } + + var options = this.options, + player = this.player; + var onLoad = this.loadImage.bind(this); + var list = []; + var total = 0; + var index = 0; + this.played = true; + this.onLoadWhenPlay = onLoad; + + if (fullscreen) { + this.requestFullscreen(); + } + + addClass(player, CLASS_SHOW); + forEach(this.items, function (item, i) { + var img = item.querySelector('img'); + var image = document.createElement('img'); + image.src = getData(img, 'originalUrl'); + image.alt = img.getAttribute('alt'); + total += 1; + addClass(image, CLASS_FADE); + toggleClass(image, CLASS_TRANSITION, options.transition); + + if (hasClass(item, CLASS_ACTIVE)) { + addClass(image, CLASS_IN); + index = i; + } + + list.push(image); + addListener(image, EVENT_LOAD, onLoad, { + once: true + }); + player.appendChild(image); + }); + + if (isNumber(options.interval) && options.interval > 0) { + var play = function play() { + _this3.playing = setTimeout(function () { + removeClass(list[index], CLASS_IN); + index += 1; + index = index < total ? index : 0; + addClass(list[index], CLASS_IN); + play(); + }, options.interval); + }; + + if (total > 1) { + play(); + } + } + + return this; + }, + // Stop play + stop: function stop() { + var _this4 = this; + + if (!this.played) { + return this; + } + + var player = this.player; + this.played = false; + clearTimeout(this.playing); + forEach(player.getElementsByTagName('img'), function (image) { + removeListener(image, EVENT_LOAD, _this4.onLoadWhenPlay); + }); + removeClass(player, CLASS_SHOW); + player.innerHTML = ''; + this.exitFullscreen(); + return this; + }, + // Enter modal mode (only available in inline mode) + full: function full() { + var _this5 = this; + + var options = this.options, + viewer = this.viewer, + image = this.image, + list = this.list; + + if (!this.isShown || this.played || this.fulled || !options.inline) { + return this; + } + + this.fulled = true; + this.open(); + addClass(this.button, CLASS_FULLSCREEN_EXIT); + + if (options.transition) { + removeClass(list, CLASS_TRANSITION); + + if (this.viewed) { + removeClass(image, CLASS_TRANSITION); + } + } + + addClass(viewer, CLASS_FIXED); + viewer.setAttribute('style', ''); + setStyle(viewer, { + zIndex: options.zIndex + }); + this.initContainer(); + this.viewerData = assign({}, this.containerData); + this.renderList(); + + if (this.viewed) { + this.initImage(function () { + _this5.renderImage(function () { + if (options.transition) { + setTimeout(function () { + addClass(image, CLASS_TRANSITION); + addClass(list, CLASS_TRANSITION); + }, 0); + } + }); + }); + } + + return this; + }, + // Exit modal mode (only available in inline mode) + exit: function exit() { + var _this6 = this; + + var options = this.options, + viewer = this.viewer, + image = this.image, + list = this.list; + + if (!this.isShown || this.played || !this.fulled || !options.inline) { + return this; + } + + this.fulled = false; + this.close(); + removeClass(this.button, CLASS_FULLSCREEN_EXIT); + + if (options.transition) { + removeClass(list, CLASS_TRANSITION); + + if (this.viewed) { + removeClass(image, CLASS_TRANSITION); + } + } + + removeClass(viewer, CLASS_FIXED); + setStyle(viewer, { + zIndex: options.zIndexInline + }); + this.viewerData = assign({}, this.parentData); + this.renderViewer(); + this.renderList(); + + if (this.viewed) { + this.initImage(function () { + _this6.renderImage(function () { + if (options.transition) { + setTimeout(function () { + addClass(image, CLASS_TRANSITION); + addClass(list, CLASS_TRANSITION); + }, 0); + } + }); + }); + } + + return this; + }, + // Show the current ratio of the image with percentage + tooltip: function tooltip() { + var _this7 = this; + + var options = this.options, + tooltipBox = this.tooltipBox, + imageData = this.imageData; + + if (!this.viewed || this.played || !options.tooltip) { + return this; + } + + tooltipBox.textContent = "".concat(Math.round(imageData.ratio * 100), "%"); + + if (!this.tooltipping) { + if (options.transition) { + if (this.fading) { + dispatchEvent(tooltipBox, EVENT_TRANSITION_END); + } + + addClass(tooltipBox, CLASS_SHOW); + addClass(tooltipBox, CLASS_FADE); + addClass(tooltipBox, CLASS_TRANSITION); // Force reflow to enable CSS3 transition + // eslint-disable-next-line + + tooltipBox.offsetWidth; + addClass(tooltipBox, CLASS_IN); + } else { + addClass(tooltipBox, CLASS_SHOW); + } + } else { + clearTimeout(this.tooltipping); + } + + this.tooltipping = setTimeout(function () { + if (options.transition) { + addListener(tooltipBox, EVENT_TRANSITION_END, function () { + removeClass(tooltipBox, CLASS_SHOW); + removeClass(tooltipBox, CLASS_FADE); + removeClass(tooltipBox, CLASS_TRANSITION); + _this7.fading = false; + }, { + once: true + }); + removeClass(tooltipBox, CLASS_IN); + _this7.fading = true; + } else { + removeClass(tooltipBox, CLASS_SHOW); + } + + _this7.tooltipping = false; + }, 1000); + return this; + }, + // Toggle the image size between its natural size and initial size + toggle: function toggle() { + if (this.imageData.ratio === 1) { + this.zoomTo(this.initialImageData.ratio, true); + } else { + this.zoomTo(1, true); + } + + return this; + }, + // Reset the image to its initial state + reset: function reset() { + if (this.viewed && !this.played) { + this.imageData = assign({}, this.initialImageData); + this.renderImage(); + } + + return this; + }, + // Update viewer when images changed + update: function update() { + var element = this.element, + options = this.options, + isImg = this.isImg; // Destroy viewer if the target image was deleted + + if (isImg && !element.parentNode) { + return this.destroy(); + } + + var images = []; + forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) { + if (options.filter) { + if (options.filter(image)) { + images.push(image); + } + } else { + images.push(image); + } + }); + + if (!images.length) { + return this; + } + + this.images = images; + this.length = images.length; + + if (this.ready) { + var indexes = []; + forEach(this.items, function (item, i) { + var img = item.querySelector('img'); + var image = images[i]; + + if (image) { + if (image.src !== img.src) { + indexes.push(i); + } + } else { + indexes.push(i); + } + }); + setStyle(this.list, { + width: 'auto' + }); + this.initList(); + + if (this.isShown) { + if (this.length) { + if (this.viewed) { + var index = indexes.indexOf(this.index); + + if (index >= 0) { + this.viewed = false; + this.view(Math.max(this.index - (index + 1), 0)); + } else { + addClass(this.items[this.index], CLASS_ACTIVE); + } + } + } else { + this.image = null; + this.viewed = false; + this.index = 0; + this.imageData = {}; + this.canvas.innerHTML = ''; + this.title.innerHTML = ''; + } + } + } else { + this.build(); + } + + return this; + }, + // Destroy the viewer + destroy: function destroy() { + var element = this.element, + options = this.options; + + if (!element[NAMESPACE]) { + return this; + } + + this.destroyed = true; + + if (this.ready) { + if (this.played) { + this.stop(); + } + + if (options.inline) { + if (this.fulled) { + this.exit(); + } + + this.unbind(); + } else if (this.isShown) { + if (this.viewing) { + if (this.imageRendering) { + this.imageRendering.abort(); + } else if (this.imageInitializing) { + this.imageInitializing.abort(); + } + } + + if (this.hiding) { + this.transitioning.abort(); + } + + this.hidden(); + } else if (this.showing) { + this.transitioning.abort(); + this.hidden(); + } + + this.ready = false; + this.viewer.parentNode.removeChild(this.viewer); + } else if (options.inline) { + if (this.delaying) { + this.delaying.abort(); + } else if (this.initializing) { + this.initializing.abort(); + } + } + + if (!options.inline) { + removeListener(element, EVENT_CLICK, this.onStart); + } + + element[NAMESPACE] = undefined; + return this; + } + }; + + var others = { + open: function open() { + var body = this.body; + addClass(body, CLASS_OPEN); + body.style.paddingRight = "".concat(this.scrollbarWidth + (parseFloat(this.initialBodyPaddingRight) || 0), "px"); + }, + close: function close() { + var body = this.body; + removeClass(body, CLASS_OPEN); + body.style.paddingRight = this.initialBodyPaddingRight; + }, + shown: function shown() { + var element = this.element, + options = this.options; + this.fulled = true; + this.isShown = true; + this.render(); + this.bind(); + this.showing = false; + + if (isFunction(options.shown)) { + addListener(element, EVENT_SHOWN, options.shown, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_SHOWN) === false) { + return; + } + + if (this.ready && this.isShown && !this.hiding) { + this.view(this.index); + } + }, + hidden: function hidden() { + var element = this.element, + options = this.options; + this.fulled = false; + this.viewed = false; + this.isShown = false; + this.close(); + this.unbind(); + addClass(this.viewer, CLASS_HIDE); + this.resetList(); + this.resetImage(); + this.hiding = false; + + if (!this.destroyed) { + if (isFunction(options.hidden)) { + addListener(element, EVENT_HIDDEN, options.hidden, { + once: true + }); + } + + dispatchEvent(element, EVENT_HIDDEN); + } + }, + requestFullscreen: function requestFullscreen() { + var document = this.element.ownerDocument; + + if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { + var documentElement = document.documentElement; // Element.requestFullscreen() + + if (documentElement.requestFullscreen) { + documentElement.requestFullscreen(); + } else if (documentElement.webkitRequestFullscreen) { + documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else if (documentElement.mozRequestFullScreen) { + documentElement.mozRequestFullScreen(); + } else if (documentElement.msRequestFullscreen) { + documentElement.msRequestFullscreen(); + } + } + }, + exitFullscreen: function exitFullscreen() { + var document = this.element.ownerDocument; + + if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { + // Document.exitFullscreen() + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } + } + }, + change: function change(event) { + var options = this.options, + pointers = this.pointers; + var pointer = pointers[Object.keys(pointers)[0]]; + var offsetX = pointer.endX - pointer.startX; + var offsetY = pointer.endY - pointer.startY; + + switch (this.action) { + // Move the current image + case ACTION_MOVE: + this.move(offsetX, offsetY); + break; + // Zoom the current image + + case ACTION_ZOOM: + this.zoom(getMaxZoomRatio(pointers), false, event); + break; + + case ACTION_SWITCH: + { + this.action = 'switched'; + var absoluteOffsetX = Math.abs(offsetX); + + if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) { + // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers. + this.pointers = {}; + + if (offsetX > 1) { + this.prev(options.loop); + } else if (offsetX < -1) { + this.next(options.loop); + } + } + + break; + } + + default: + } // Override + + + forEach(pointers, function (p) { + p.startX = p.endX; + p.startY = p.endY; + }); + }, + isSwitchable: function isSwitchable() { + var imageData = this.imageData, + viewerData = this.viewerData; + return this.length > 1 && imageData.left >= 0 && imageData.top >= 0 && imageData.width <= viewerData.width && imageData.height <= viewerData.height; + } + }; + + var AnotherViewer = WINDOW.Viewer; + + var Viewer = + /*#__PURE__*/ + function () { + /** + * Create a new Viewer. + * @param {Element} element - The target element for viewing. + * @param {Object} [options={}] - The configuration options. + */ + function Viewer(element) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, Viewer); + + if (!element || element.nodeType !== 1) { + throw new Error('The first argument is required and must be an element.'); + } + + this.element = element; + this.options = assign({}, DEFAULTS, isPlainObject(options) && options); + this.action = false; + this.fading = false; + this.fulled = false; + this.hiding = false; + this.imageClicked = false; + this.imageData = {}; + this.index = this.options.initialViewIndex; + this.isImg = false; + this.isShown = false; + this.length = 0; + this.played = false; + this.playing = false; + this.pointers = {}; + this.ready = false; + this.showing = false; + this.timeout = false; + this.tooltipping = false; + this.viewed = false; + this.viewing = false; + this.wheeling = false; + this.zooming = false; + this.init(); + } + + _createClass(Viewer, [{ + key: "init", + value: function init() { + var _this = this; + + var element = this.element, + options = this.options; + + if (element[NAMESPACE]) { + return; + } + + element[NAMESPACE] = this; + var isImg = element.tagName.toLowerCase() === 'img'; + var images = []; + forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) { + if (isFunction(options.filter)) { + if (options.filter.call(_this, image)) { + images.push(image); + } + } else { + images.push(image); + } + }); + this.isImg = isImg; + this.length = images.length; + this.images = images; + var ownerDocument = element.ownerDocument; + var body = ownerDocument.body || ownerDocument.documentElement; + this.body = body; + this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth; + this.initialBodyPaddingRight = window.getComputedStyle(body).paddingRight; // Override `transition` option if it is not supported + + if (isUndefined(document.createElement(NAMESPACE).style.transition)) { + options.transition = false; + } + + if (options.inline) { + var count = 0; + + var progress = function progress() { + count += 1; + + if (count === _this.length) { + var timeout; + _this.initializing = false; + _this.delaying = { + abort: function abort() { + clearTimeout(timeout); + } + }; // build asynchronously to keep `this.viewer` is accessible in `ready` event handler. + + timeout = setTimeout(function () { + _this.delaying = false; + + _this.build(); + }, 0); + } + }; + + this.initializing = { + abort: function abort() { + forEach(images, function (image) { + if (!image.complete) { + removeListener(image, EVENT_LOAD, progress); + } + }); + } + }; + forEach(images, function (image) { + if (image.complete) { + progress(); + } else { + addListener(image, EVENT_LOAD, progress, { + once: true + }); + } + }); + } else { + addListener(element, EVENT_CLICK, this.onStart = function (_ref) { + var target = _ref.target; + + if (target.tagName.toLowerCase() === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) { + _this.view(_this.images.indexOf(target)); + } + }); + } + } + }, { + key: "build", + value: function build() { + if (this.ready) { + return; + } + + var element = this.element, + options = this.options; + var parent = element.parentNode; + var template = document.createElement('div'); + template.innerHTML = TEMPLATE; + var viewer = template.querySelector(".".concat(NAMESPACE, "-container")); + var title = viewer.querySelector(".".concat(NAMESPACE, "-title")); + var toolbar = viewer.querySelector(".".concat(NAMESPACE, "-toolbar")); + var navbar = viewer.querySelector(".".concat(NAMESPACE, "-navbar")); + var button = viewer.querySelector(".".concat(NAMESPACE, "-button")); + var canvas = viewer.querySelector(".".concat(NAMESPACE, "-canvas")); + this.parent = parent; + this.viewer = viewer; + this.title = title; + this.toolbar = toolbar; + this.navbar = navbar; + this.button = button; + this.canvas = canvas; + this.footer = viewer.querySelector(".".concat(NAMESPACE, "-footer")); + this.tooltipBox = viewer.querySelector(".".concat(NAMESPACE, "-tooltip")); + this.player = viewer.querySelector(".".concat(NAMESPACE, "-player")); + this.list = viewer.querySelector(".".concat(NAMESPACE, "-list")); + addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title)); + addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar)); + toggleClass(button, CLASS_HIDE, !options.button); + + if (options.backdrop) { + addClass(viewer, "".concat(NAMESPACE, "-backdrop")); + + if (!options.inline && options.backdrop !== 'static') { + setData(canvas, DATA_ACTION, 'hide'); + } + } + + if (isString(options.className) && options.className) { + // In case there are multiple class names + options.className.split(REGEXP_SPACES).forEach(function (className) { + addClass(viewer, className); + }); + } + + if (options.toolbar) { + var list = document.createElement('ul'); + var custom = isPlainObject(options.toolbar); + var zoomButtons = BUTTONS.slice(0, 3); + var rotateButtons = BUTTONS.slice(7, 9); + var scaleButtons = BUTTONS.slice(9); + + if (!custom) { + addClass(toolbar, getResponsiveClass(options.toolbar)); + } + + forEach(custom ? options.toolbar : BUTTONS, function (value, index) { + var deep = custom && isPlainObject(value); + var name = custom ? hyphenate(index) : value; + var show = deep && !isUndefined(value.show) ? value.show : value; + + if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) { + return; + } + + var size = deep && !isUndefined(value.size) ? value.size : value; + var click = deep && !isUndefined(value.click) ? value.click : value; + var item = document.createElement('li'); + item.setAttribute('role', 'button'); + addClass(item, "".concat(NAMESPACE, "-").concat(name)); + + if (!isFunction(click)) { + setData(item, DATA_ACTION, name); + } + + if (isNumber(show)) { + addClass(item, getResponsiveClass(show)); + } + + if (['small', 'large'].indexOf(size) !== -1) { + addClass(item, "".concat(NAMESPACE, "-").concat(size)); + } else if (name === 'play') { + addClass(item, "".concat(NAMESPACE, "-large")); + } + + if (isFunction(click)) { + addListener(item, EVENT_CLICK, click); + } + + list.appendChild(item); + }); + toolbar.appendChild(list); + } else { + addClass(toolbar, CLASS_HIDE); + } + + if (!options.rotatable) { + var rotates = toolbar.querySelectorAll('li[class*="rotate"]'); + addClass(rotates, CLASS_INVISIBLE); + forEach(rotates, function (rotate) { + toolbar.appendChild(rotate); + }); + } + + if (options.inline) { + addClass(button, CLASS_FULLSCREEN); + setStyle(viewer, { + zIndex: options.zIndexInline + }); + + if (window.getComputedStyle(parent).position === 'static') { + setStyle(parent, { + position: 'relative' + }); + } + + parent.insertBefore(viewer, element.nextSibling); + } else { + addClass(button, CLASS_CLOSE); + addClass(viewer, CLASS_FIXED); + addClass(viewer, CLASS_FADE); + addClass(viewer, CLASS_HIDE); + setStyle(viewer, { + zIndex: options.zIndex + }); + var container = options.container; + + if (isString(container)) { + container = element.ownerDocument.querySelector(container); + } + + if (!container) { + container = this.body; + } + + container.appendChild(viewer); + } + + if (options.inline) { + this.render(); + this.bind(); + this.isShown = true; + } + + this.ready = true; + + if (isFunction(options.ready)) { + addListener(element, EVENT_READY, options.ready, { + once: true + }); + } + + if (dispatchEvent(element, EVENT_READY) === false) { + this.ready = false; + return; + } + + if (this.ready && options.inline) { + this.view(this.index); + } + } + /** + * Get the no conflict viewer class. + * @returns {Viewer} The viewer class. + */ + + }], [{ + key: "noConflict", + value: function noConflict() { + window.Viewer = AnotherViewer; + return Viewer; + } + /** + * Change the default options. + * @param {Object} options - The new default options. + */ + + }, { + key: "setDefaults", + value: function setDefaults(options) { + assign(DEFAULTS, isPlainObject(options) && options); + } + }]); + + return Viewer; + }(); + + assign(Viewer.prototype, render, events, handlers, methods, others); + + return Viewer; + +})); diff --git a/src/main/resources/templates/graduation/dist/viewer/js/viewer.min.js b/src/main/resources/templates/graduation/dist/viewer/js/viewer.min.js new file mode 100644 index 0000000000000000000000000000000000000000..65e6ea1d38c31e099401dfa453202456d82eed05 --- /dev/null +++ b/src/main/resources/templates/graduation/dist/viewer/js/viewer.min.js @@ -0,0 +1,10 @@ +/*! + * Viewer.js v1.3.3 + * https://fengyuanchen.github.io/viewerjs + * + * Copyright 2015-present Chen Fengyuan + * Released under the MIT license + * + * Date: 2019-04-06T14:06:28.301Z + */ +!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(t=t||self).Viewer=i()}(this,function(){"use strict";function i(t){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function n(t,i){for(var e=0;e")}),i.innerHTML=r.join(""),this.items=i.getElementsByTagName("li"),nt(this.items,function(i){var t=i.firstElementChild;ft(t,"filled",!0),a.loading&&ht(i,v),pt(t,Y,function(t){a.loading&<(i,v),o.loadImage(t)},{once:!0})}),a.transition&&pt(t,V,function(){ht(i,I)},{once:!0})},renderList:function(t){var i=t||this.index,e=this.items[i].offsetWidth||30,n=e+1;at(this.list,st({width:n*this.length},bt({translateX:(this.viewerData.width-e)/2-n*i})))},resetList:function(){var t=this.list;t.innerHTML="",lt(t,I),at(t,bt({translateX:0}))},initImage:function(r){var t,h=this,l=this.options,i=this.image,e=this.viewerData,n=this.footer.offsetHeight,c=e.width,u=Math.max(e.height-n,n),d=this.imageData||{};this.imageInitializing={abort:function(){t.onload=null}},t=xt(i,function(t,i){var e=t/i,n=c,s=u;h.imageInitializing=!1,c=this.length||this.viewed&&t===this.index)return this;this.viewing&&this.viewing.abort();var i=this.element,n=this.options,s=this.title,o=this.canvas,a=this.items[t],r=a.querySelector("img"),h=mt(r,"originalUrl"),l=r.getAttribute("alt"),c=document.createElement("img");if(c.src=h,c.alt=l,et(n.view)&&pt(i,j,n.view,{once:!0}),!1===wt(i,j,{originalImage:this.images[t],index:t,image:c})||!this.isShown||this.hiding||this.played)return this;this.image=c,lt(this.items[this.index],m),ht(a,m),this.viewed=!1,this.index=t,this.imageData={},ht(c,z),n.loading&&ht(o,v),o.innerHTML="",o.appendChild(c),this.renderList(),s.innerHTML="";var u,d=function(){var t=e.imageData,i=Array.isArray(n.title)?n.title[1]:n.title;s.innerHTML=et(i)?i.call(e,c,t):"".concat(l," (").concat(t.naturalWidth," × ").concat(t.naturalHeight,")")};return pt(i,V,d,{once:!0}),this.viewing={abort:function(){vt(i,V,d),c.complete?this.imageRendering?this.imageRendering.abort():this.imageInitializing&&this.imageInitializing.abort():(c.src="",vt(c,Y,u),this.timeout&&clearTimeout(this.timeout))}},c.complete?this.load():(pt(c,Y,u=this.load.bind(this),{once:!0}),this.timeout&&clearTimeout(this.timeout),this.timeout=setTimeout(function(){lt(c,z),e.timeout=!1},1e3)),this},prev:function(){var t=0Math.abs(o)&&(this.pointers={},1
      ';var n=e.querySelector(".".concat(p,"-container")),s=n.querySelector(".".concat(p,"-title")),o=n.querySelector(".".concat(p,"-toolbar")),a=n.querySelector(".".concat(p,"-navbar")),r=n.querySelector(".".concat(p,"-button")),l=n.querySelector(".".concat(p,"-canvas"));if(this.parent=i,this.viewer=n,this.title=s,this.toolbar=o,this.navbar=a,this.button=r,this.canvas=l,this.footer=n.querySelector(".".concat(p,"-footer")),this.tooltipBox=n.querySelector(".".concat(p,"-tooltip")),this.player=n.querySelector(".".concat(p,"-player")),this.list=n.querySelector(".".concat(p,"-list")),ht(s,h.title?kt(Array.isArray(h.title)?h.title[0]:h.title):k),ht(a,h.navbar?kt(h.navbar):k),ct(r,k,!h.button),h.backdrop&&(ht(n,"".concat(p,"-backdrop")),h.inline||"static"===h.backdrop||ft(l,K,"hide")),$(h.className)&&h.className&&h.className.split(U).forEach(function(t){ht(n,t)}),h.toolbar){var c=document.createElement("ul"),u=it(h.toolbar),d=Z.slice(0,3),m=Z.slice(7,9),f=Z.slice(9);u||ht(o,kt(h.toolbar)),nt(u?h.toolbar:Z,function(t,i){var e=u&&it(t),n=u?dt(i):t,s=e&&!J(t.show)?t.show:t;if(s&&(h.zoomable||-1===d.indexOf(n))&&(h.rotatable||-1===m.indexOf(n))&&(h.scalable||-1===f.indexOf(n))){var o=e&&!J(t.size)?t.size:t,a=e&&!J(t.click)?t.click:t,r=document.createElement("li");r.setAttribute("role","button"),ht(r,"".concat(p,"-").concat(n)),et(a)||ft(r,K,n),G(s)&&ht(r,kt(s)),-1!==["small","large"].indexOf(o)?ht(r,"".concat(p,"-").concat(o)):"play"===n&&ht(r,"".concat(p,"-large")),et(a)&&pt(r,S,a),c.appendChild(r)}}),o.appendChild(c)}else ht(o,k);if(!h.rotatable){var g=o.querySelectorAll('li[class*="rotate"]');ht(g,z),nt(g,function(t){o.appendChild(t)})}if(h.inline)ht(r,x),at(n,{zIndex:h.zIndexInline}),"static"===window.getComputedStyle(i).position&&at(i,{position:"relative"}),i.insertBefore(n,t.nextSibling);else{ht(r,w),ht(n,y),ht(n,b),ht(n,k),at(n,{zIndex:h.zIndex});var v=h.container;$(v)&&(v=t.ownerDocument.querySelector(v)),v||(v=this.body),v.appendChild(n)}h.inline&&(this.render(),this.bind(),this.isShown=!0),this.ready=!0,et(h.ready)&&pt(t,O,h.ready,{once:!0}),!1!==wt(t,O)?this.ready&&h.inline&&this.view(this.index):this.ready=!1}}}],[{key:"noConflict",value:function(){return window.Viewer=Ct,e}},{key:"setDefaults",value:function(t){st(s,it(t)&&t)}}]),e}();return st(Lt.prototype,Dt,Tt,Et,It,St),Lt}); \ No newline at end of file diff --git a/src/main/resources/templates/graduation/dist/wow/js/wow.min.js b/src/main/resources/templates/graduation/dist/wow/js/wow.min.js new file mode 100644 index 0000000000000000000000000000000000000000..f2ca6591cfb8a0d7235fec4fdc987ec14b89cc26 --- /dev/null +++ b/src/main/resources/templates/graduation/dist/wow/js/wow.min.js @@ -0,0 +1,2 @@ +/*! WOW - v1.0.1 - 2014-09-03 +* Copyright (c) 2014 Matthieu Aussaguel; Licensed MIT */(function(){var a,b,c,d,e,f=function(a,b){return function(){return a.apply(b,arguments)}},g=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};b=function(){function a(){}return a.prototype.extend=function(a,b){var c,d;for(c in b)d=b[c],null==a[c]&&(a[c]=d);return a},a.prototype.isMobile=function(a){return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(a)},a.prototype.addEvent=function(a,b,c){return null!=a.addEventListener?a.addEventListener(b,c,!1):null!=a.attachEvent?a.attachEvent("on"+b,c):a[b]=c},a.prototype.removeEvent=function(a,b,c){return null!=a.removeEventListener?a.removeEventListener(b,c,!1):null!=a.detachEvent?a.detachEvent("on"+b,c):delete a[b]},a.prototype.innerHeight=function(){return"innerHeight"in window?window.innerHeight:document.documentElement.clientHeight},a}(),c=this.WeakMap||this.MozWeakMap||(c=function(){function a(){this.keys=[],this.values=[]}return a.prototype.get=function(a){var b,c,d,e,f;for(f=this.keys,b=d=0,e=f.length;e>d;b=++d)if(c=f[b],c===a)return this.values[b]},a.prototype.set=function(a,b){var c,d,e,f,g;for(g=this.keys,c=e=0,f=g.length;f>e;c=++e)if(d=g[c],d===a)return void(this.values[c]=b);return this.keys.push(a),this.values.push(b)},a}()),a=this.MutationObserver||this.WebkitMutationObserver||this.MozMutationObserver||(a=function(){function a(){"undefined"!=typeof console&&null!==console&&console.warn("MutationObserver is not supported by your browser."),"undefined"!=typeof console&&null!==console&&console.warn("WOW.js cannot detect dom mutations, please call .sync() after loading new content.")}return a.notSupported=!0,a.prototype.observe=function(){},a}()),d=this.getComputedStyle||function(a){return this.getPropertyValue=function(b){var c;return"float"===b&&(b="styleFloat"),e.test(b)&&b.replace(e,function(a,b){return b.toUpperCase()}),(null!=(c=a.currentStyle)?c[b]:void 0)||null},this},e=/(\-([a-z]){1})/g,this.WOW=function(){function e(a){null==a&&(a={}),this.scrollCallback=f(this.scrollCallback,this),this.scrollHandler=f(this.scrollHandler,this),this.start=f(this.start,this),this.scrolled=!0,this.config=this.util().extend(a,this.defaults),this.animationNameCache=new c}return e.prototype.defaults={boxClass:"wow",animateClass:"animated",offset:0,mobile:!0,live:!0},e.prototype.init=function(){var a;return this.element=window.document.documentElement,"interactive"===(a=document.readyState)||"complete"===a?this.start():this.util().addEvent(document,"DOMContentLoaded",this.start),this.finished=[]},e.prototype.start=function(){var b,c,d,e;if(this.stopped=!1,this.boxes=function(){var a,c,d,e;for(d=this.element.querySelectorAll("."+this.config.boxClass),e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(b);return e}.call(this),this.all=function(){var a,c,d,e;for(d=this.boxes,e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(b);return e}.call(this),this.boxes.length)if(this.disabled())this.resetStyle();else{for(e=this.boxes,c=0,d=e.length;d>c;c++)b=e[c],this.applyStyle(b,!0);this.util().addEvent(window,"scroll",this.scrollHandler),this.util().addEvent(window,"resize",this.scrollHandler),this.interval=setInterval(this.scrollCallback,50)}return this.config.live?new a(function(a){return function(b){var c,d,e,f,g;for(g=[],e=0,f=b.length;f>e;e++)d=b[e],g.push(function(){var a,b,e,f;for(e=d.addedNodes||[],f=[],a=0,b=e.length;b>a;a++)c=e[a],f.push(this.doSync(c));return f}.call(a));return g}}(this)).observe(document.body,{childList:!0,subtree:!0}):void 0},e.prototype.stop=function(){return this.stopped=!0,this.util().removeEvent(window,"scroll",this.scrollHandler),this.util().removeEvent(window,"resize",this.scrollHandler),null!=this.interval?clearInterval(this.interval):void 0},e.prototype.sync=function(){return a.notSupported?this.doSync(this.element):void 0},e.prototype.doSync=function(a){var b,c,d,e,f;if(!this.stopped){if(null==a&&(a=this.element),1!==a.nodeType)return;for(a=a.parentNode||a,e=a.querySelectorAll("."+this.config.boxClass),f=[],c=0,d=e.length;d>c;c++)b=e[c],g.call(this.all,b)<0?(this.applyStyle(b,!0),this.boxes.push(b),this.all.push(b),f.push(this.scrolled=!0)):f.push(void 0);return f}},e.prototype.show=function(a){return this.applyStyle(a),a.className=""+a.className+" "+this.config.animateClass},e.prototype.applyStyle=function(a,b){var c,d,e;return d=a.getAttribute("data-wow-duration"),c=a.getAttribute("data-wow-delay"),e=a.getAttribute("data-wow-iteration"),this.animate(function(f){return function(){return f.customStyle(a,b,d,c,e)}}(this))},e.prototype.animate=function(){return"requestAnimationFrame"in window?function(a){return window.requestAnimationFrame(a)}:function(a){return a()}}(),e.prototype.resetStyle=function(){var a,b,c,d,e;for(d=this.boxes,e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(a.setAttribute("style","visibility: visible;"));return e},e.prototype.customStyle=function(a,b,c,d,e){return b&&this.cacheAnimationName(a),a.style.visibility=b?"hidden":"visible",c&&this.vendorSet(a.style,{animationDuration:c}),d&&this.vendorSet(a.style,{animationDelay:d}),e&&this.vendorSet(a.style,{animationIterationCount:e}),this.vendorSet(a.style,{animationName:b?"none":this.cachedAnimationName(a)}),a},e.prototype.vendors=["moz","webkit"],e.prototype.vendorSet=function(a,b){var c,d,e,f;f=[];for(c in b)d=b[c],a[""+c]=d,f.push(function(){var b,f,g,h;for(g=this.vendors,h=[],b=0,f=g.length;f>b;b++)e=g[b],h.push(a[""+e+c.charAt(0).toUpperCase()+c.substr(1)]=d);return h}.call(this));return f},e.prototype.vendorCSS=function(a,b){var c,e,f,g,h,i;for(e=d(a),c=e.getPropertyCSSValue(b),i=this.vendors,g=0,h=i.length;h>g;g++)f=i[g],c=c||e.getPropertyCSSValue("-"+f+"-"+b);return c},e.prototype.animationName=function(a){var b;try{b=this.vendorCSS(a,"animation-name").cssText}catch(c){b=d(a).getPropertyValue("animation-name")}return"none"===b?"":b},e.prototype.cacheAnimationName=function(a){return this.animationNameCache.set(a,this.animationName(a))},e.prototype.cachedAnimationName=function(a){return this.animationNameCache.get(a)},e.prototype.scrollHandler=function(){return this.scrolled=!0},e.prototype.scrollCallback=function(){var a;return!this.scrolled||(this.scrolled=!1,this.boxes=function(){var b,c,d,e;for(d=this.boxes,e=[],b=0,c=d.length;c>b;b++)a=d[b],a&&(this.isVisible(a)?this.show(a):e.push(a));return e}.call(this),this.boxes.length||this.config.live)?void 0:this.stop()},e.prototype.offsetTop=function(a){for(var b;void 0===a.offsetTop;)a=a.parentNode;for(b=a.offsetTop;a=a.offsetParent;)b+=a.offsetTop;return b},e.prototype.isVisible=function(a){var b,c,d,e,f;return c=a.getAttribute("data-wow-offset")||this.config.offset,f=window.pageYOffset,e=f+Math.min(this.element.clientHeight,this.util().innerHeight())-c,d=this.offsetTop(a),b=d+a.clientHeight,e>=d&&b>=f},e.prototype.util=function(){return null!=this._util?this._util:this._util=new b},e.prototype.disabled=function(){return!this.config.mobile&&this.util().isMobile(navigator.userAgent)},e}()}).call(this); \ No newline at end of file diff --git a/src/main/resources/templates/classic/inc/action_message.ftl b/src/main/resources/templates/graduation/inc/action_message.ftl similarity index 100% rename from src/main/resources/templates/classic/inc/action_message.ftl rename to src/main/resources/templates/graduation/inc/action_message.ftl diff --git a/src/main/resources/templates/classic/inc/footer.ftl b/src/main/resources/templates/graduation/inc/footer.ftl similarity index 58% rename from src/main/resources/templates/classic/inc/footer.ftl rename to src/main/resources/templates/graduation/inc/footer.ftl index 689698df20412db51cbb7e33c0dca8f77d6e7375..d499fac2e8ac297640ad6db8a01f3ff5d11b54ae 100644 --- a/src/main/resources/templates/classic/inc/footer.ftl +++ b/src/main/resources/templates/graduation/inc/footer.ftl @@ -8,10 +8,10 @@ ${options['site_copyright']} ${options['site_icp']} - +<#-- --> @@ -25,3 +25,6 @@ main.init(); }); + + + diff --git a/src/main/resources/templates/classic/inc/header.ftl b/src/main/resources/templates/graduation/inc/header.ftl similarity index 68% rename from src/main/resources/templates/classic/inc/header.ftl rename to src/main/resources/templates/graduation/inc/header.ftl index 0476b78ef407a8272a8e735bc67f2fd2ac27d310..89a88a87ff8a5cd54e7cf61e1cc37a27ec61dba1 100644 --- a/src/main/resources/templates/classic/inc/header.ftl +++ b/src/main/resources/templates/graduation/inc/header.ftl @@ -1,24 +1,22 @@ + + + + - - -