mirror of
https://github.com/zfile-dev/zfile.git
synced 2025-04-19 05:34:52 +00:00
🔖 版本更新
This commit is contained in:
43
pom.xml
43
pom.xml
@@ -33,6 +33,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
@@ -86,11 +90,11 @@
|
||||
<version>3.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.nuxeo.onedrive</groupId>
|
||||
<artifactId>onedrive-java-client</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.nuxeo.onedrive</groupId>-->
|
||||
<!-- <artifactId>onedrive-java-client</artifactId>-->
|
||||
<!-- <version>1.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -147,14 +151,33 @@
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-validator</groupId>
|
||||
<artifactId>commons-validator</artifactId>
|
||||
<version>1.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>28.1-jre</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>nuxeo</id>
|
||||
<name>nuxeo</name>
|
||||
<url>http://maven.nuxeo.org/nexus/content/groups/public</url>
|
||||
</repository>
|
||||
<!-- <repository>-->
|
||||
<!-- <id>nuxeo</id>-->
|
||||
<!-- <name>nuxeo</name>-->
|
||||
<!-- <url>http://maven.nuxeo.org/nexus/content/groups/public</url>-->
|
||||
<!-- </repository>-->
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -4,7 +4,9 @@ import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
@EnableAsync
|
||||
@SpringBootApplication
|
||||
@EnableCaching
|
||||
@EnableAspectJAutoProxy(exposeProxy = true)
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
package im.zhaojun.aliyun.service;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.aliyun.oss.OSS;
|
||||
import com.aliyun.oss.OSSClientBuilder;
|
||||
import com.aliyun.oss.model.*;
|
||||
import im.zhaojun.common.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.FileItem;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class AliyunService implements FileService {
|
||||
|
||||
@Value("${zfile.cache.timeout}")
|
||||
private Long timeout;
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
private static final String BUCKET_NAME_KEY = "bucket-name";
|
||||
|
||||
private static final String ACCESS_KEY = "accessKey";
|
||||
|
||||
private static final String SECRET_KEY = "secretKey";
|
||||
|
||||
private static final String DOMAIN_KEY = "domain";
|
||||
|
||||
private static final String ENDPOINT_KEY = "endPoint";
|
||||
|
||||
private OSS ossClient;
|
||||
|
||||
private String bucketName;
|
||||
|
||||
private String domain;
|
||||
|
||||
private boolean isPrivate;
|
||||
|
||||
@Override
|
||||
public void initMethod() {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.ALIYUN);
|
||||
String accessKey = stringStorageConfigMap.get(ACCESS_KEY).getValue();
|
||||
String secretKey = stringStorageConfigMap.get(SECRET_KEY).getValue();
|
||||
String endPoint = stringStorageConfigMap.get(ENDPOINT_KEY).getValue();
|
||||
|
||||
bucketName = stringStorageConfigMap.get(BUCKET_NAME_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
ossClient = new OSSClientBuilder().build(endPoint, accessKey, secretKey);
|
||||
|
||||
AccessControlList bucketAcl = ossClient.getBucketAcl(bucketName);
|
||||
CannedAccessControlList cannedACL = bucketAcl.getCannedACL();
|
||||
isPrivate = "Private".equals(cannedACL.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItem> fileList(String path) {
|
||||
path = StringUtils.removeFirstSeparator(path);
|
||||
|
||||
List<FileItem> fileItemList = new ArrayList<>();
|
||||
ObjectListing objectListing =
|
||||
ossClient.listObjects(new ListObjectsRequest(bucketName).withDelimiter("/").withPrefix(path));
|
||||
|
||||
for (OSSObjectSummary s : objectListing.getObjectSummaries()) {
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setName(s.getKey().substring(path.length()));
|
||||
fileItem.setSize(s.getSize());
|
||||
fileItem.setTime(s.getLastModified());
|
||||
fileItem.setType(FileTypeEnum.FILE);
|
||||
fileItem.setPath(path);
|
||||
fileItemList.add(fileItem);
|
||||
}
|
||||
|
||||
for (String commonPrefix : objectListing.getCommonPrefixes()) {
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setName(commonPrefix.substring(path.length(), commonPrefix.length() - 1));
|
||||
fileItem.setType(FileTypeEnum.FOLDER);
|
||||
fileItem.setPath(path);
|
||||
fileItemList.add(fileItem);
|
||||
}
|
||||
|
||||
return fileItemList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl(String path) throws Exception {
|
||||
path = StringUtils.removeFirstSeparator(path);
|
||||
|
||||
if (isPrivate) {
|
||||
Date expirationDate = new Date(new Date().getTime() + timeout * 1000);
|
||||
URL url = ossClient.generatePresignedUrl(bucketName, path, expirationDate);
|
||||
return URLUtil.complateUrl(domain, url.getFile());
|
||||
} else {
|
||||
return URLUtil.complateUrl(domain, path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.ALIYUN;
|
||||
}
|
||||
}
|
||||
131
src/main/java/im/zhaojun/aliyun/service/AliyunServiceImpl.java
Normal file
131
src/main/java/im/zhaojun/aliyun/service/AliyunServiceImpl.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package im.zhaojun.aliyun.service;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.aliyun.oss.OSS;
|
||||
import com.aliyun.oss.OSSClientBuilder;
|
||||
import com.aliyun.oss.model.*;
|
||||
import im.zhaojun.common.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.dto.FileItemDTO;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class AliyunServiceImpl implements FileService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AliyunServiceImpl.class);
|
||||
|
||||
@Value("${zfile.cache.timeout}")
|
||||
private Long timeout;
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
private static final String BUCKET_NAME_KEY = "bucket-name";
|
||||
|
||||
private static final String ACCESS_KEY = "accessKey";
|
||||
|
||||
private static final String SECRET_KEY = "secretKey";
|
||||
|
||||
private static final String DOMAIN_KEY = "domain";
|
||||
|
||||
private static final String ENDPOINT_KEY = "endPoint";
|
||||
|
||||
private OSS ossClient;
|
||||
|
||||
private String bucketName;
|
||||
|
||||
private String domain;
|
||||
|
||||
private boolean isPrivate;
|
||||
|
||||
private boolean isInitialized;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
try {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.ALIYUN);
|
||||
String accessKey = stringStorageConfigMap.get(ACCESS_KEY).getValue();
|
||||
String secretKey = stringStorageConfigMap.get(SECRET_KEY).getValue();
|
||||
String endPoint = stringStorageConfigMap.get(ENDPOINT_KEY).getValue();
|
||||
|
||||
bucketName = stringStorageConfigMap.get(BUCKET_NAME_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
ossClient = new OSSClientBuilder().build(endPoint, accessKey, secretKey);
|
||||
|
||||
AccessControlList bucketAcl = ossClient.getBucketAcl(bucketName);
|
||||
CannedAccessControlList cannedAcl = bucketAcl.getCannedACL();
|
||||
isPrivate = "Private".equals(cannedAcl.name());
|
||||
isInitialized = true;
|
||||
} catch (Exception e) {
|
||||
log.debug(StorageTypeEnum.ALIYUN.getDescription() + "初始化异常, 已跳过");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItemDTO> fileList(String path) {
|
||||
path = StringUtils.removeFirstSeparator(path);
|
||||
|
||||
List<FileItemDTO> fileItemList = new ArrayList<>();
|
||||
ObjectListing objectListing =
|
||||
ossClient.listObjects(new ListObjectsRequest(bucketName).withDelimiter("/").withPrefix(path));
|
||||
|
||||
for (OSSObjectSummary s : objectListing.getObjectSummaries()) {
|
||||
FileItemDTO fileItemDTO = new FileItemDTO();
|
||||
fileItemDTO.setName(s.getKey().substring(path.length()));
|
||||
fileItemDTO.setSize(s.getSize());
|
||||
fileItemDTO.setTime(s.getLastModified());
|
||||
fileItemDTO.setType(FileTypeEnum.FILE);
|
||||
fileItemDTO.setPath(path);
|
||||
fileItemDTO.setUrl(getDownloadUrl(StringUtils.concatUrl(path, fileItemDTO.getName())));
|
||||
fileItemList.add(fileItemDTO);
|
||||
}
|
||||
|
||||
for (String commonPrefix : objectListing.getCommonPrefixes()) {
|
||||
FileItemDTO fileItemDTO = new FileItemDTO();
|
||||
fileItemDTO.setName(commonPrefix.substring(path.length(), commonPrefix.length() - 1));
|
||||
fileItemDTO.setType(FileTypeEnum.FOLDER);
|
||||
fileItemDTO.setPath(path);
|
||||
fileItemList.add(fileItemDTO);
|
||||
}
|
||||
|
||||
return fileItemList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl(String path) {
|
||||
path = StringUtils.removeFirstSeparator(path);
|
||||
|
||||
if (isPrivate) {
|
||||
Date expirationDate = new Date(System.currentTimeMillis() + timeout * 1000);
|
||||
URL url = ossClient.generatePresignedUrl(bucketName, path, expirationDate);
|
||||
return URLUtil.complateUrl(domain, url.getFile());
|
||||
} else {
|
||||
return URLUtil.complateUrl(domain, path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.ALIYUN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getIsInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package im.zhaojun.common.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 标记注解, 用于在调用前检查是否已存储策略
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CheckStorageStrategyInit {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package im.zhaojun.common.aspect;
|
||||
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.exception.StorageStrategyUninitializedException;
|
||||
import im.zhaojun.common.service.SystemConfigService;
|
||||
import im.zhaojun.common.util.SpringContextHolder;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
public class StorageStrategyInitCheckAspect {
|
||||
|
||||
@Before("@annotation(im.zhaojun.common.annotation.CheckStorageStrategyInit)")
|
||||
public void logStart() {
|
||||
SystemConfigService systemConfigService = SpringContextHolder.getBean(SystemConfigService.class);
|
||||
StorageTypeEnum currentStorageStrategy = systemConfigService.getCurrentStorageStrategy();
|
||||
|
||||
if (currentStorageStrategy == null) {
|
||||
throw new StorageStrategyUninitializedException("存储策略未初始化");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +1,36 @@
|
||||
package im.zhaojun.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.cors.CorsUtils;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.annotation.WebFilter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
@WebFilter(filterName = "CorsFilter")
|
||||
@Configuration
|
||||
public class CorsFilter implements Filter {
|
||||
/**
|
||||
* 开启跨域支持. 一般用于开发环境, 或前后端分离部署时开启.
|
||||
*/
|
||||
public class CorsFilter extends GenericFilterBean {
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
|
||||
|
||||
// Set customized header
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, httpServletRequest.getHeader(HttpHeaders.ORIGIN));
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Origin, X-Requested-With, Content-Type, Accept");
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS");
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
|
||||
|
||||
if (!CorsUtils.isPreFlightRequest(httpServletRequest)) {
|
||||
chain.doFilter(httpServletRequest, httpServletResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
response.setHeader("Access-Control-Allow-Credentials", "true");
|
||||
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
|
||||
response.setHeader("Access-Control-Max-Age", "3600");
|
||||
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package im.zhaojun.common.config;
|
||||
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.exception.UnknownStorageTypeException;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -39,10 +38,6 @@ public class StorageTypeFactory implements ApplicationContextAware {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
throw new UnknownStorageTypeException(type.getDescription());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
15
src/main/java/im/zhaojun/common/config/WebMvcConfig.java
Normal file
15
src/main/java/im/zhaojun/common/config/WebMvcConfig.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package im.zhaojun.common.config;
|
||||
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnumDeSerializerConvert;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverter(new StorageTypeEnumDeSerializerConvert());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package im.zhaojun.common.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.cache.interceptor.KeyGenerator;
|
||||
@@ -15,11 +14,11 @@ import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* 缓存配置类, 用于根据配置决定使用 redis 缓存还是 caffeine (内存).
|
||||
*/
|
||||
@Configuration
|
||||
public class ZfileCacheConfiguration {
|
||||
|
||||
@Value("${zfile.cache.timeout}")
|
||||
private Long timeout;
|
||||
public class ZFileCacheConfiguration {
|
||||
|
||||
public static final String CACHE_NAME = "zfile";
|
||||
|
||||
@@ -34,6 +33,20 @@ public class ZfileCacheConfiguration {
|
||||
return caffeineCacheManager;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "spring.cache.type", havingValue = "redis")
|
||||
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
|
||||
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
|
||||
GenericJackson2JsonRedisSerializer jsonRedisSerializer
|
||||
= new GenericJackson2JsonRedisSerializer();
|
||||
RedisSerializationContext.SerializationPair<Object> pair
|
||||
= RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);
|
||||
RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
|
||||
return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public KeyGenerator keyGenerator() {
|
||||
return (target, method, params) -> {
|
||||
@@ -59,17 +72,4 @@ public class ZfileCacheConfiguration {
|
||||
return strBuilder.toString();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "spring.cache.type", havingValue = "redis")
|
||||
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
|
||||
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
|
||||
GenericJackson2JsonRedisSerializer jsonRedisSerializer
|
||||
= new GenericJackson2JsonRedisSerializer();
|
||||
RedisSerializationContext.SerializationPair<Object> pair
|
||||
= RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);
|
||||
RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
|
||||
return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import org.springframework.web.client.RestTemplate;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Configuration
|
||||
public class RestConfig {
|
||||
public class ZFileConfiguration {
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate(){
|
||||
@@ -16,4 +16,5 @@ public class RestConfig {
|
||||
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
|
||||
return restTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package im.zhaojun.common.constant;
|
||||
|
||||
public class ZfileConstant {
|
||||
|
||||
public static final String HEADER_FILE_NAME = "header.md";
|
||||
|
||||
public static final String FOOTER_FILE_NAME = "footer.md";
|
||||
|
||||
public static final String PASSWORD_FILE_NAME = "password.txt";
|
||||
|
||||
}
|
||||
115
src/main/java/im/zhaojun/common/controller/AdminController.java
Normal file
115
src/main/java/im/zhaojun/common/controller/AdminController.java
Normal file
@@ -0,0 +1,115 @@
|
||||
package im.zhaojun.common.controller;
|
||||
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.dto.ResultBean;
|
||||
import im.zhaojun.common.model.SystemConfigDTO;
|
||||
import im.zhaojun.common.service.FileAsyncCacheService;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.service.SystemConfigService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 后台管理
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
public class AdminController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AdminController.class);
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
@Resource
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
@Resource
|
||||
private FileAsyncCacheService fileAsyncCacheService;
|
||||
|
||||
/**
|
||||
* 获取系统配置
|
||||
*/
|
||||
@GetMapping("/config")
|
||||
public ResultBean getConfig() {
|
||||
SystemConfigDTO systemConfigDTO = systemConfigService.getSystemConfig();
|
||||
return ResultBean.success(systemConfigDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统配置
|
||||
*/
|
||||
@PostMapping("/update-pwd")
|
||||
public ResultBean updatePwd(String username, String password) {
|
||||
systemConfigService.updateUsernameAndPwd(username, password);
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统配置
|
||||
*/
|
||||
@PostMapping("/config")
|
||||
public ResultBean updateConfig(SystemConfigDTO systemConfigDTO) {
|
||||
StorageTypeEnum currentStorageStrategy = systemConfigService.getCurrentStorageStrategy();
|
||||
|
||||
systemConfigDTO.setId(1);
|
||||
systemConfigService.updateSystemConfig(systemConfigDTO);
|
||||
|
||||
if (!currentStorageStrategy.equals(systemConfigDTO.getStorageStrategy())) {
|
||||
refreshStorageStrategy();
|
||||
}
|
||||
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/strategy-form")
|
||||
public ResultBean getFormByStorageType(StorageTypeEnum storageType) {
|
||||
List<StorageConfig> storageConfigList = storageConfigService.selectStorageConfigByType(storageType);
|
||||
return ResultBean.success(storageConfigList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理当前启用的存储引擎的缓存
|
||||
*/
|
||||
@GetMapping("/clear-cache")
|
||||
public ResultBean clearCache() {
|
||||
FileService fileService = systemConfigService.getCurrentFileService();
|
||||
fileService.clearCache();
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新存储策略
|
||||
*/
|
||||
public void refreshStorageStrategy() {
|
||||
StorageTypeEnum storageStrategy = systemConfigService.getCurrentStorageStrategy();
|
||||
refreshStorageStrategy(storageStrategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新存储策略
|
||||
*/
|
||||
public void refreshStorageStrategy(StorageTypeEnum storageStrategy) {
|
||||
if (storageStrategy == null) {
|
||||
log.info("尚未配置存储策略.");
|
||||
} else {
|
||||
FileService fileService = systemConfigService.getCurrentFileService();
|
||||
fileService.init();
|
||||
log.info("当前启用存储类型: {}", storageStrategy.getDescription());
|
||||
|
||||
// if 判断是否开启搜索.
|
||||
fileAsyncCacheService.cacheGlobalFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
package im.zhaojun.common.controller;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import im.zhaojun.common.config.StorageTypeFactory;
|
||||
import im.zhaojun.common.constant.ZfileConstant;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.annotation.CheckStorageStrategyInit;
|
||||
import im.zhaojun.common.model.constant.ZFileConstant;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.exception.SearchDisableException;
|
||||
import im.zhaojun.common.model.FileItem;
|
||||
import im.zhaojun.common.model.ResultBean;
|
||||
import im.zhaojun.common.model.SiteConfig;
|
||||
import im.zhaojun.common.model.ViewConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.SystemService;
|
||||
import im.zhaojun.common.service.ViewConfigService;
|
||||
import im.zhaojun.common.model.*;
|
||||
import im.zhaojun.common.model.dto.FileItemDTO;
|
||||
import im.zhaojun.common.model.dto.ResultBean;
|
||||
import im.zhaojun.common.model.dto.SiteConfigDTO;
|
||||
import im.zhaojun.common.service.*;
|
||||
import im.zhaojun.common.util.AudioHelper;
|
||||
import im.zhaojun.common.util.FileComparator;
|
||||
import im.zhaojun.common.util.HttpUtil;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -21,38 +21,43 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 前台文件管理
|
||||
*/
|
||||
@RequestMapping("/api")
|
||||
@RestController
|
||||
public class FileController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FileController.class);
|
||||
|
||||
private FileService fileService;
|
||||
|
||||
@Resource
|
||||
private SystemService systemService;
|
||||
|
||||
@Resource
|
||||
private ViewConfigService viewConfigService;
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
public static final Integer PAGE_SIZE = 20;
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
/**
|
||||
* 滚动加载每页条数.
|
||||
*/
|
||||
private static final Integer PAGE_SIZE = 20;
|
||||
|
||||
@CheckStorageStrategyInit
|
||||
@GetMapping("/list")
|
||||
public ResultBean list(@RequestParam(defaultValue = "/") String path,
|
||||
@RequestParam(defaultValue = "name") String sortBy,
|
||||
@RequestParam(defaultValue = "asc") String order,
|
||||
@RequestParam(required = false) String password,
|
||||
@RequestParam(defaultValue = "1") Integer page) throws Exception {
|
||||
List<FileItem> fileItems = fileService.fileList(StringUtils.removeDuplicateSeparator("/" + URLUtil.decode(path)));
|
||||
|
||||
for (FileItem fileItem : fileItems) {
|
||||
if (ZfileConstant.PASSWORD_FILE_NAME.equals(fileItem.getName())) {
|
||||
if (!fileService.getTextContent(fileItem.getUrl()).equals(password)) {
|
||||
FileService fileService = systemConfigService.getCurrentFileService();
|
||||
List<FileItemDTO> fileItemList = fileService.fileList(StringUtils.removeDuplicateSeparator("/" + URLUtil.decode(path) + "/"));
|
||||
for (FileItemDTO fileItemDTO : fileItemList) {
|
||||
if (ZFileConstant.PASSWORD_FILE_NAME.equals(fileItemDTO.getName())) {
|
||||
if (!HttpUtil.getTextContent(fileItemDTO.getUrl()).equals(password)) {
|
||||
if (password != null && !"".equals(password)) {
|
||||
return ResultBean.error("密码错误.");
|
||||
}
|
||||
@@ -62,10 +67,10 @@ public class FileController {
|
||||
}
|
||||
|
||||
// 排序, 先按照文件类型比较, 文件夹在前, 文件在后, 然后根据 sortBy 字段排序, 默认为升序;
|
||||
fileItems.sort(new FileComparator(sortBy, order));
|
||||
filterFileList(fileItems);
|
||||
fileItemList.sort(new FileComparator(sortBy, order));
|
||||
filterFileList(fileItemList);
|
||||
|
||||
Integer total = fileItems.size();
|
||||
Integer total = fileItemList.size();
|
||||
Integer totalPage = (total + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||
|
||||
if (page > totalPage) {
|
||||
@@ -75,7 +80,7 @@ public class FileController {
|
||||
Integer start = (page - 1) * PAGE_SIZE;
|
||||
Integer end = page * PAGE_SIZE;
|
||||
end = end > total ? total : end;
|
||||
List<FileItem> fileSubItem = fileItems.subList(start, end);
|
||||
List<FileItemDTO> fileSubItem = fileItemList.subList(start, end);
|
||||
return ResultBean.successData(fileSubItem);
|
||||
}
|
||||
|
||||
@@ -84,83 +89,70 @@ public class FileController {
|
||||
* @param url 文件路径
|
||||
* @return 文件内容
|
||||
*/
|
||||
@CheckStorageStrategyInit
|
||||
@GetMapping("/content")
|
||||
public ResultBean getContent(String url) throws Exception {
|
||||
return ResultBean.successData(fileService.getTextContent(url));
|
||||
public ResultBean getContent(String url) {
|
||||
return ResultBean.successData(HttpUtil.getTextContent(url));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统配置信息和当前页的标题, 文件头, 文件尾信息
|
||||
* @param path 路径
|
||||
*/
|
||||
@CheckStorageStrategyInit
|
||||
@GetMapping("/config")
|
||||
public ResultBean getConfig(String path) throws Exception {
|
||||
SiteConfig config = systemService.getConfig(URLUtil.decode(StringUtils.removeDuplicateSeparator("/" + path + "/")));
|
||||
config.setViewConfig(viewConfigService.getViewConfig());
|
||||
SiteConfigDTO config = systemService.getConfig(URLUtil.decode(StringUtils.removeDuplicateSeparator("/" + path + "/")));
|
||||
config.setSystemConfigDTO(systemConfigService.getSystemConfig());
|
||||
return ResultBean.successData(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新存储策略, 使用 @PostConstruct 注解, 以用于第一次启动时, 根据数据库的配置值, 读取默认的存储策略.
|
||||
*/
|
||||
@PostConstruct
|
||||
@GetMapping("/updateStorageStrategy")
|
||||
public ResultBean updateConfig() {
|
||||
ViewConfig viewConfig = viewConfigService.getViewConfig();
|
||||
StorageTypeEnum storageStrategy = viewConfig.getStorageStrategy();
|
||||
fileService = StorageTypeFactory.getStorageTypeService(storageStrategy);
|
||||
log.info("当前启用存储类型: {}", storageStrategy.getDescription());
|
||||
// new Thread(() -> {
|
||||
// log.info("缓存 {} 所有文件开始", storageStrategy.getDescription());
|
||||
// long startTime = System.currentTimeMillis();
|
||||
// try {
|
||||
// fileService.selectAllFileList();
|
||||
// } catch (Exception e) {
|
||||
// log.error("缓存所有文件失败", e);
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// long endTime = System.currentTimeMillis();
|
||||
// log.info("缓存 {} 所有文件结束, 用时: {} 秒", storageStrategy.getDescription(), ( (endTime - startTime) / 1000 ));
|
||||
// }).start();
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
@CheckStorageStrategyInit
|
||||
@GetMapping("/clearCache")
|
||||
public ResultBean clearCache() throws Exception {
|
||||
fileService.clearCache();
|
||||
public ResultBean clearCache() {
|
||||
FileService fileService = systemConfigService.getCurrentFileService();
|
||||
if (fileService != null) {
|
||||
fileService.clearCache();
|
||||
}
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
@GetMapping("/getImageInfo")
|
||||
public ResultBean getImageInfo(String url) throws Exception {
|
||||
return ResultBean.success(fileService.getImageInfo(url));
|
||||
}
|
||||
|
||||
@CheckStorageStrategyInit
|
||||
@GetMapping("/audioInfo")
|
||||
public ResultBean getAudioInfo(String url) throws Exception {
|
||||
return ResultBean.success(fileService.getAudioInfo(url));
|
||||
return ResultBean.success(AudioHelper.getAudioInfo(url));
|
||||
}
|
||||
|
||||
@CheckStorageStrategyInit
|
||||
@GetMapping("/search")
|
||||
public ResultBean search(@RequestParam(value = "name", defaultValue = "/") String name) throws Exception {
|
||||
ViewConfig viewConfig = viewConfigService.getViewConfig();
|
||||
if (!viewConfig.getSearchEnable()) {
|
||||
FileService fileService = systemConfigService.getCurrentFileService();
|
||||
SystemConfigDTO systemConfigDTO = systemConfigService.getSystemConfig();
|
||||
if (!systemConfigDTO.getSearchEnable()) {
|
||||
throw new SearchDisableException("搜索功能未开启");
|
||||
}
|
||||
return ResultBean.success(fileService.search(URLUtil.decode(name)));
|
||||
}
|
||||
|
||||
@GetMapping("/form")
|
||||
public ResultBean getFormByStorageType(String storageType) {
|
||||
StorageTypeEnum storageTypeEnum = StorageTypeEnum.getEnum(storageType);
|
||||
List<StorageConfig> storageConfigList = storageConfigService.selectStorageConfigByType(storageTypeEnum);
|
||||
storageConfigList.forEach(storageConfig -> storageConfig.setValue(null));
|
||||
return ResultBean.success(storageConfigList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤文件列表, 不显示密码, 头部和尾部文件.
|
||||
*/
|
||||
private void filterFileList(List<FileItem> fileItemList) {
|
||||
private void filterFileList(List<FileItemDTO> fileItemList) {
|
||||
if (fileItemList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileItemList.removeIf(fileItem -> ZfileConstant.PASSWORD_FILE_NAME.equals(fileItem.getName())
|
||||
|| ZfileConstant.FOOTER_FILE_NAME.equals(fileItem.getName())
|
||||
|| ZfileConstant.HEADER_FILE_NAME.equals(fileItem.getName()));
|
||||
fileItemList.removeIf(fileItem -> ZFileConstant.PASSWORD_FILE_NAME.equals(fileItem.getName())
|
||||
|| ZFileConstant.FOOTER_FILE_NAME.equals(fileItem.getName())
|
||||
|| ZFileConstant.HEADER_FILE_NAME.equals(fileItem.getName()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package im.zhaojun.common.controller;
|
||||
|
||||
import im.zhaojun.common.service.ViewConfigService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
@Resource
|
||||
private ViewConfigService viewConfigService;
|
||||
|
||||
@GetMapping("/")
|
||||
public String index() {
|
||||
return "redirect:/file/";
|
||||
}
|
||||
|
||||
@GetMapping("/file/**")
|
||||
public ModelAndView index(ModelAndView modelAndView) {
|
||||
modelAndView.setViewName("index");
|
||||
modelAndView.addObject("viewConfig", viewConfigService.getViewConfig());
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
@GetMapping("/admin")
|
||||
public ModelAndView admin(ModelAndView modelAndView) {
|
||||
modelAndView.setViewName("admin");
|
||||
modelAndView.addObject("viewConfig", viewConfigService.getViewConfig());
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
@GetMapping("/install")
|
||||
public ModelAndView install(ModelAndView modelAndView) {
|
||||
modelAndView.setViewName("install");
|
||||
return modelAndView;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package im.zhaojun.common.controller;
|
||||
|
||||
import im.zhaojun.common.config.WebMvcConfig;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.model.SystemConfigDTO;
|
||||
import im.zhaojun.common.model.dto.InstallModelDTO;
|
||||
import im.zhaojun.common.model.dto.ResultBean;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.service.SystemConfigService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 系统安装初始化
|
||||
*/
|
||||
@Controller
|
||||
public class InstallController {
|
||||
|
||||
@Resource
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
@Resource
|
||||
private AdminController adminController;
|
||||
|
||||
@GetMapping("/is-installed")
|
||||
@ResponseBody
|
||||
public ResultBean isInstall() {
|
||||
if (systemConfigService.getCurrentStorageStrategy() == null) {
|
||||
return ResultBean.success();
|
||||
}
|
||||
return ResultBean.error("请勿重复初始化");
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/install")
|
||||
@ResponseBody
|
||||
public ResultBean install(InstallModelDTO installModelDTO) {
|
||||
SystemConfigDTO systemConfigDTO = systemConfigService.getSystemConfig();
|
||||
|
||||
if (systemConfigDTO.getStorageStrategy() != null) {
|
||||
return ResultBean.error("请勿重复初始化.");
|
||||
}
|
||||
|
||||
systemConfigDTO.setSiteName(installModelDTO.getSiteName());
|
||||
StorageTypeEnum storageTypeEnum = installModelDTO.getStorageStrategy();
|
||||
systemConfigDTO.setStorageStrategy(storageTypeEnum);
|
||||
systemConfigDTO.setUsername(installModelDTO.getUsername());
|
||||
systemConfigDTO.setPassword(new BCryptPasswordEncoder().encode(installModelDTO.getPassword()));
|
||||
systemConfigService.updateSystemConfig(systemConfigDTO);
|
||||
|
||||
Map<String, String> storageStrategyConfig = installModelDTO.getStorageStrategyConfig();
|
||||
|
||||
List<StorageConfig> storageConfigList = storageConfigService.selectStorageConfigByType(storageTypeEnum);
|
||||
for (StorageConfig storageConfig : storageConfigList) {
|
||||
String key = storageConfig.getKey();
|
||||
String value = storageStrategyConfig.get(key);
|
||||
storageConfig.setValue(value);
|
||||
}
|
||||
|
||||
storageConfigService.updateStorageConfig(storageConfigList);
|
||||
adminController.refreshStorageStrategy();
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
@PostMapping("/storage-strategy")
|
||||
@ResponseBody
|
||||
public ResultBean save(@RequestParam Map<String, String> storageStrategyConfig, StorageTypeEnum storageStrategy) {
|
||||
List<StorageConfig> storageConfigList = storageConfigService.selectStorageConfigByType(storageStrategy);
|
||||
for (StorageConfig storageConfig : storageConfigList) {
|
||||
String key = storageConfig.getKey();
|
||||
String value = storageStrategyConfig.get(key);
|
||||
storageConfig.setValue(value);
|
||||
}
|
||||
storageConfigService.updateStorageConfig(storageConfigList);
|
||||
|
||||
StorageTypeEnum currentStorageStrategy = systemConfigService.getCurrentStorageStrategy();
|
||||
if (Objects.equals(storageStrategy, currentStorageStrategy)) {
|
||||
FileService fileService = systemConfigService.getCurrentFileService();
|
||||
fileService.clearCache();
|
||||
fileService.init();
|
||||
}
|
||||
|
||||
return ResultBean.success();
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package im.zhaojun.common.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum ViewModeEnum {
|
||||
|
||||
DETAILS("details"), ICONS("icons"), GRID("grid");
|
||||
|
||||
private static Map<String, ViewModeEnum> enumMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (ViewModeEnum type : ViewModeEnum.values()) {
|
||||
enumMap.put(type.getValue(), type);
|
||||
}
|
||||
}
|
||||
|
||||
String value;
|
||||
|
||||
ViewModeEnum(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ViewModeEnum getEnum(String value) {
|
||||
return enumMap.get(value);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package im.zhaojun.common.enums;
|
||||
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
@Converter(autoApply = true)
|
||||
public class ViewModeEnumConvert implements AttributeConverter<ViewModeEnum, String> {
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(ViewModeEnum attribute) {
|
||||
return attribute.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewModeEnum convertToEntityAttribute(String dbData) {
|
||||
return ViewModeEnum.getEnum(dbData);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package im.zhaojun.common.exception;
|
||||
|
||||
import im.zhaojun.common.model.ResultBean;
|
||||
import im.zhaojun.common.model.dto.ResultBean;
|
||||
import org.apache.catalina.connector.ClientAbortException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
@@ -27,4 +29,35 @@ public class GlobleExceptionHandler {
|
||||
return ResultBean.error(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler
|
||||
@ResponseStatus
|
||||
public ResultBean searchDisableExceptionHandler(StorageStrategyUninitializedException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(e.getMessage(), e);
|
||||
}
|
||||
return ResultBean.error(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获 ClientAbortException 异常, 不做任何处理, 防止出现大量堆栈日志输出, 此异常不影响功能.
|
||||
*/
|
||||
@ExceptionHandler({HttpMediaTypeNotAcceptableException.class, ClientAbortException.class})
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public void clientAbortException(Exception ex) {
|
||||
// if (log.isDebugEnabled()) {
|
||||
// log.debug("出现了断开异常:", ex);
|
||||
// }
|
||||
}
|
||||
|
||||
@ExceptionHandler
|
||||
@ResponseStatus(code= HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ResultBean searchDisableExceptionHandler(Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(e.getMessage(), e);
|
||||
}
|
||||
return ResultBean.error("系统异常, 请联系管理员");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package im.zhaojun.common.exception;
|
||||
|
||||
/**
|
||||
* 对象存储初始化异常
|
||||
*/
|
||||
public class InitializeException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -1920550904063819880L;
|
||||
|
||||
public InitializeException() {
|
||||
}
|
||||
|
||||
public InitializeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InitializeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public InitializeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public InitializeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package im.zhaojun.common.exception;
|
||||
|
||||
/**
|
||||
* 存储策略未初始化异常
|
||||
*/
|
||||
public class StorageStrategyUninitializedException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 5736940575583615661L;
|
||||
|
||||
public StorageStrategyUninitializedException() {
|
||||
}
|
||||
|
||||
public StorageStrategyUninitializedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public StorageStrategyUninitializedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public StorageStrategyUninitializedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public StorageStrategyUninitializedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package im.zhaojun.common.model;
|
||||
|
||||
public class FolderItem {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package im.zhaojun.common.model;
|
||||
|
||||
public class ImageInfo {
|
||||
private Integer width;
|
||||
private Integer height;
|
||||
|
||||
public ImageInfo() {}
|
||||
|
||||
public ImageInfo(Integer width, Integer height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public Integer getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(Integer width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public Integer getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(Integer height) {
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package im.zhaojun.common.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class SiteConfig implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 8811196207046121740L;
|
||||
|
||||
private String header;
|
||||
|
||||
private String footer;
|
||||
|
||||
private ViewConfig viewConfig;
|
||||
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public void setHeader(String header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
public String getFooter() {
|
||||
return footer;
|
||||
}
|
||||
|
||||
public void setFooter(String footer) {
|
||||
this.footer = footer;
|
||||
}
|
||||
|
||||
public ViewConfig getViewConfig() {
|
||||
return viewConfig;
|
||||
}
|
||||
|
||||
public void setViewConfig(ViewConfig viewConfig) {
|
||||
this.viewConfig = viewConfig;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
package im.zhaojun.common.model;
|
||||
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Entity(name = "STORAGE_CONFIG")
|
||||
@Data
|
||||
public class StorageConfig {
|
||||
|
||||
@@ -18,6 +15,7 @@ public class StorageConfig {
|
||||
|
||||
private StorageTypeEnum type;
|
||||
|
||||
@Column(name = "`key`")
|
||||
private String key;
|
||||
|
||||
private String title;
|
||||
|
||||
54
src/main/java/im/zhaojun/common/model/SystemConfig.java
Normal file
54
src/main/java/im/zhaojun/common/model/SystemConfig.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package im.zhaojun.common.model;
|
||||
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
@Entity(name = "SYSTEM_CONFIG")
|
||||
@Data
|
||||
public class SystemConfig {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
|
||||
@Column(name = "`key`")
|
||||
private String key;
|
||||
|
||||
private String value;
|
||||
|
||||
private String remark;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,31 @@
|
||||
package im.zhaojun.common.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.enums.ViewModeEnum;
|
||||
import lombok.Data;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnumSerializerConvert;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity(name = "SYSTEM_CONFIG")
|
||||
@Data
|
||||
public class ViewConfig {
|
||||
public class SystemConfigDTO {
|
||||
|
||||
@JsonIgnore
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
|
||||
private String siteName;
|
||||
|
||||
private ViewModeEnum mode;
|
||||
|
||||
private Boolean sidebarEnable;
|
||||
|
||||
private Boolean infoEnable;
|
||||
|
||||
private Boolean searchEnable;
|
||||
|
||||
private Boolean searchIgnoreCase;
|
||||
|
||||
@JsonIgnore
|
||||
@JsonSerialize(using = StorageTypeEnumSerializerConvert.class)
|
||||
private StorageTypeEnum storageStrategy;
|
||||
|
||||
private String username;
|
||||
|
||||
@JsonIgnore
|
||||
private String password;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
@@ -50,22 +42,6 @@ public class ViewConfig {
|
||||
this.siteName = siteName;
|
||||
}
|
||||
|
||||
public ViewModeEnum getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(ViewModeEnum mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public Boolean getSidebarEnable() {
|
||||
return sidebarEnable;
|
||||
}
|
||||
|
||||
public void setSidebarEnable(Boolean sidebarEnable) {
|
||||
this.sidebarEnable = sidebarEnable;
|
||||
}
|
||||
|
||||
public Boolean getInfoEnable() {
|
||||
return infoEnable;
|
||||
}
|
||||
@@ -97,4 +73,20 @@ public class ViewConfig {
|
||||
public void setStorageStrategy(StorageTypeEnum storageStrategy) {
|
||||
this.storageStrategy = storageStrategy;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package im.zhaojun.common.model.constant;
|
||||
|
||||
public class SystemConfigConstant {
|
||||
|
||||
public static final String SITE_NAME = "siteName";
|
||||
|
||||
public static final String INFO_ENABLE = "infoEnable";
|
||||
|
||||
public static final String SEARCH_ENABLE = "searchEnable";
|
||||
|
||||
public static final String SEARCH_IGNORE_CASE = "searchIgnoreCase";
|
||||
|
||||
public static final String STORAGE_STRATEGY = "storageStrategy";
|
||||
|
||||
public static final String USERNAME = "username";
|
||||
|
||||
public static final String PASSWORD = "password";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package im.zhaojun.common.model.constant;
|
||||
|
||||
public class ZFileConstant {
|
||||
|
||||
public final static String USER_HOME = System.getProperty("user.home");
|
||||
|
||||
public static final String AUDIO_TMP_PATH = "/.zfile/tmp/audio/";
|
||||
|
||||
/**
|
||||
* 页面头部文件
|
||||
*/
|
||||
public static final String HEADER_FILE_NAME = "header.md";
|
||||
|
||||
/**
|
||||
* 页面尾部文件
|
||||
*/
|
||||
public static final String FOOTER_FILE_NAME = "footer.md";
|
||||
|
||||
/**
|
||||
* 密码文件
|
||||
*/
|
||||
public static final String PASSWORD_FILE_NAME = "password.txt";
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.common.model;
|
||||
package im.zhaojun.common.model.dto;
|
||||
|
||||
public class AudioInfo {
|
||||
public class AudioInfoDTO {
|
||||
private String title;
|
||||
private String artist;
|
||||
private String cover;
|
||||
@@ -37,4 +37,14 @@ public class AudioInfo {
|
||||
public void setSrc(String src) {
|
||||
this.src = src;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AudioInfoDTO{" +
|
||||
"title='" + title + '\'' +
|
||||
", artist='" + artist + '\'' +
|
||||
", cover='" + cover + '\'' +
|
||||
", src='" + src + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package im.zhaojun.common.model;
|
||||
package im.zhaojun.common.model.dto;
|
||||
|
||||
import im.zhaojun.common.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.model.enums.FileTypeEnum;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
public class FileItem implements Serializable {
|
||||
public class FileItemDTO implements Serializable {
|
||||
|
||||
private String name;
|
||||
private Date time;
|
||||
@@ -61,4 +61,16 @@ public class FileItem implements Serializable {
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FileItemDTO{" +
|
||||
"name='" + name + '\'' +
|
||||
", time=" + time +
|
||||
", size=" + size +
|
||||
", type=" + type +
|
||||
", path='" + path + '\'' +
|
||||
", url='" + url + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package im.zhaojun.common.model.dto;
|
||||
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class InstallModelDTO {
|
||||
private String siteName;
|
||||
private StorageTypeEnum storageStrategy;
|
||||
private String username;
|
||||
private String password;
|
||||
private Map<String, String> storageStrategyConfig;
|
||||
|
||||
public String getSiteName() {
|
||||
return siteName;
|
||||
}
|
||||
|
||||
public void setSiteName(String siteName) {
|
||||
this.siteName = siteName;
|
||||
}
|
||||
|
||||
public StorageTypeEnum getStorageStrategy() {
|
||||
return storageStrategy;
|
||||
}
|
||||
|
||||
public void setStorageStrategy(StorageTypeEnum storageStrategy) {
|
||||
this.storageStrategy = storageStrategy;
|
||||
}
|
||||
|
||||
public Map<String, String> getStorageStrategyConfig() {
|
||||
return storageStrategyConfig;
|
||||
}
|
||||
|
||||
public void setStorageStrategyConfig(Map<String, String> storageStrategyConfig) {
|
||||
this.storageStrategyConfig = storageStrategyConfig;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InstallModelDTO{" +
|
||||
"siteName='" + siteName + '\'' +
|
||||
", storageStrategy=" + storageStrategy +
|
||||
", username='" + username + '\'' +
|
||||
", password='" + password + '\'' +
|
||||
", storageStrategyConfig=" + storageStrategyConfig +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package im.zhaojun.common.model;
|
||||
package im.zhaojun.common.model.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
49
src/main/java/im/zhaojun/common/model/dto/SiteConfigDTO.java
Normal file
49
src/main/java/im/zhaojun/common/model/dto/SiteConfigDTO.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package im.zhaojun.common.model.dto;
|
||||
|
||||
import im.zhaojun.common.model.SystemConfigDTO;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class SiteConfigDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 8811196207046121740L;
|
||||
|
||||
private String header;
|
||||
|
||||
private String footer;
|
||||
|
||||
private SystemConfigDTO systemConfigDTO;
|
||||
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public void setHeader(String header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
public String getFooter() {
|
||||
return footer;
|
||||
}
|
||||
|
||||
public void setFooter(String footer) {
|
||||
this.footer = footer;
|
||||
}
|
||||
|
||||
public SystemConfigDTO getSystemConfigDTO() {
|
||||
return systemConfigDTO;
|
||||
}
|
||||
|
||||
public void setSystemConfigDTO(SystemConfigDTO systemConfigDTO) {
|
||||
this.systemConfigDTO = systemConfigDTO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SiteConfigDTO{" +
|
||||
"header='" + header + '\'' +
|
||||
", footer='" + footer + '\'' +
|
||||
", systemConfig=" + systemConfigDTO +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,16 @@
|
||||
package im.zhaojun.common.enums;
|
||||
package im.zhaojun.common.model.enums;
|
||||
|
||||
public enum FileTypeEnum {
|
||||
FILE("File"), FOLDER("Folder");
|
||||
|
||||
/**
|
||||
* 文件
|
||||
*/
|
||||
FILE("File"),
|
||||
|
||||
/**
|
||||
* 文件夹
|
||||
*/
|
||||
FOLDER("Folder");
|
||||
|
||||
private String value;
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
package im.zhaojun.common.enums;
|
||||
package im.zhaojun.common.model.enums;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum StorageTypeEnum {
|
||||
|
||||
/**
|
||||
* 当前系统支持的所有存储策略
|
||||
*/
|
||||
UPYUN("upyun", "又拍云 USS"),
|
||||
QINIU("qiniu", "七牛云 KODO"),
|
||||
HUAWEI("huawei", "华为云 OBS"),
|
||||
ALIYUN("aliyun", "阿里云 OSS"),
|
||||
FTP("ftp", "FTP"),
|
||||
LOCAL("local", "本地存储"),
|
||||
TENCENT("tencent", "腾讯云 COS");;
|
||||
TENCENT("tencent", "腾讯云 COS");
|
||||
|
||||
private static Map<String, StorageTypeEnum> enumMap = new HashMap<>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package im.zhaojun.common.enums;
|
||||
package im.zhaojun.common.model.enums;
|
||||
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
@@ -0,0 +1,11 @@
|
||||
package im.zhaojun.common.model.enums;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
public class StorageTypeEnumDeSerializerConvert implements Converter<String, StorageTypeEnum> {
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum convert(String s) {
|
||||
return StorageTypeEnum.getEnum(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package im.zhaojun.common.model.enums;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class StorageTypeEnumSerializerConvert extends JsonSerializer<StorageTypeEnum> {
|
||||
|
||||
@Override
|
||||
public void serialize(StorageTypeEnum storageTypeEnum, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
||||
jsonGenerator.writeString(storageTypeEnum.getKey());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package im.zhaojun.common.repository;
|
||||
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -10,6 +10,6 @@ import java.util.List;
|
||||
@Repository
|
||||
public interface StorageConfigRepository extends JpaRepository<StorageConfig, Integer> {
|
||||
|
||||
public List<StorageConfig> findByType(StorageTypeEnum type);
|
||||
List<StorageConfig> findByTypeOrderById(StorageTypeEnum type);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package im.zhaojun.common.repository;
|
||||
|
||||
import im.zhaojun.common.model.SystemConfig;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface SystemConfigRepository extends JpaRepository<SystemConfig, Integer> {
|
||||
|
||||
SystemConfig findByKey(String key);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package im.zhaojun.common.repository;
|
||||
|
||||
import im.zhaojun.common.model.ViewConfig;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface ViewConfigRepository extends JpaRepository<ViewConfig, Integer> {
|
||||
|
||||
public ViewConfig findFirstBy();
|
||||
|
||||
}
|
||||
23
src/main/java/im/zhaojun/common/security/MyCorsFilter.java
Normal file
23
src/main/java/im/zhaojun/common/security/MyCorsFilter.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package im.zhaojun.common.security;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
@Configuration
|
||||
public class MyCorsFilter {
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
|
||||
final CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.setAllowCredentials(true);
|
||||
corsConfiguration.addAllowedOrigin("*");
|
||||
corsConfiguration.addAllowedHeader("*");
|
||||
corsConfiguration.addAllowedMethod("*");
|
||||
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
|
||||
return new CorsFilter(urlBasedCorsConfigurationSource);
|
||||
}
|
||||
}
|
||||
124
src/main/java/im/zhaojun/common/security/MySecurityConfig.java
Normal file
124
src/main/java/im/zhaojun/common/security/MySecurityConfig.java
Normal file
@@ -0,0 +1,124 @@
|
||||
package im.zhaojun.common.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import im.zhaojun.common.model.dto.ResultBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* 自定义Security配置类
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// .authenticationProvider(authenticationProvider())
|
||||
.exceptionHandling()
|
||||
//未登录时,进行json格式的提示,很喜欢这种写法,不用单独写一个又一个的类
|
||||
.authenticationEntryPoint((request, response, authException) -> {
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
PrintWriter out = response.getWriter();
|
||||
out.write(objectMapper.writeValueAsString(ResultBean.error("未登录")));
|
||||
out.flush();
|
||||
out.close();
|
||||
})
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/").permitAll()
|
||||
.antMatchers("/admin/**").authenticated()
|
||||
.and()
|
||||
.formLogin() //使用自带的登录
|
||||
//登录失败,返回json
|
||||
.failureHandler((request, response, ex) -> {
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
PrintWriter out = response.getWriter();
|
||||
String msg;
|
||||
if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {
|
||||
msg = "用户名或密码错误";
|
||||
} else {
|
||||
msg = "登录失败";
|
||||
}
|
||||
out.write(objectMapper.writeValueAsString(ResultBean.error(msg)));
|
||||
out.flush();
|
||||
out.close();
|
||||
})
|
||||
//登录成功,返回json
|
||||
.successHandler((request, response, authentication) -> {
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
PrintWriter out = response.getWriter();
|
||||
out.write(objectMapper.writeValueAsString(ResultBean.success(authentication)));
|
||||
out.flush();
|
||||
out.close();
|
||||
})
|
||||
.and()
|
||||
.exceptionHandling()
|
||||
//没有权限,返回json
|
||||
.accessDeniedHandler((request, response, ex) -> {
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
PrintWriter out = response.getWriter();
|
||||
out.write(objectMapper.writeValueAsString(ResultBean.error("权限不足")));
|
||||
out.flush();
|
||||
out.close();
|
||||
})
|
||||
.and()
|
||||
.logout()
|
||||
//退出成功,返回json
|
||||
.logoutSuccessHandler((request, response, authentication) -> {
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
PrintWriter out = response.getWriter();
|
||||
out.write(objectMapper.writeValueAsString(ResultBean.error("注销成功")));
|
||||
out.flush();
|
||||
out.close();
|
||||
})
|
||||
.and()
|
||||
.logout().permitAll();
|
||||
|
||||
http.cors();
|
||||
http.csrf().disable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(AuthenticationManagerBuilder web) throws Exception {
|
||||
web.userDetailsService(myUserDetailsServiceImpl()).passwordEncoder(passwordEncoder());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MyUserDetailsServiceImpl myUserDetailsServiceImpl() {
|
||||
return new MyUserDetailsServiceImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) {
|
||||
//对于在header里面增加token等类似情况,放行所有OPTIONS请求。
|
||||
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package im.zhaojun.common.security;
|
||||
|
||||
import im.zhaojun.common.model.SystemConfigDTO;
|
||||
import im.zhaojun.common.service.SystemConfigService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collections;
|
||||
|
||||
public class MyUserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
@Resource
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
/**
|
||||
* 授权的时候是对角色授权,认证的时候应该基于资源,而不是角色,因为资源是不变的,而用户的角色是会变的
|
||||
*/
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
return new User(systemConfig.getUsername(), systemConfig.getPassword(), Collections.emptyList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package im.zhaojun.common.service;
|
||||
|
||||
import im.zhaojun.common.config.StorageTypeFactory;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Service
|
||||
public class FileAsyncCacheService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FileAsyncCacheService.class);
|
||||
|
||||
@Resource
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
@Async
|
||||
public void cacheGlobalFile() {
|
||||
StorageTypeEnum storageStrategy = systemConfigService.getCurrentStorageStrategy();
|
||||
|
||||
if (storageStrategy == null) {
|
||||
log.info("尚未配置存储策略. 跳过启动缓存.");
|
||||
return;
|
||||
}
|
||||
|
||||
FileService fileService = StorageTypeFactory.getStorageTypeService(storageStrategy);
|
||||
log.info("缓存 {} 所有文件开始", storageStrategy.getDescription());
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
if (fileService.getIsInitialized()) {
|
||||
fileService.selectAllFileList();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("缓存所有文件失败", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
long endTime = System.currentTimeMillis();
|
||||
log.info("缓存 {} 所有文件结束, 用时: {} 秒", storageStrategy.getDescription(), ( (endTime - startTime) / 1000 ));
|
||||
}
|
||||
}
|
||||
@@ -1,122 +1,65 @@
|
||||
package im.zhaojun.common.service;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import im.zhaojun.common.config.ZfileCacheConfiguration;
|
||||
import im.zhaojun.common.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.AudioInfo;
|
||||
import im.zhaojun.common.model.FileItem;
|
||||
import im.zhaojun.common.model.ImageInfo;
|
||||
import im.zhaojun.common.util.AudioHelper;
|
||||
import im.zhaojun.common.util.SpringContextHolder;
|
||||
import im.zhaojun.common.config.ZFileCacheConfiguration;
|
||||
import im.zhaojun.common.model.dto.FileItemDTO;
|
||||
import im.zhaojun.common.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.springframework.aop.framework.AopContext;
|
||||
import org.springframework.cache.annotation.CacheConfig;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@CacheConfig(cacheNames = ZfileCacheConfiguration.CACHE_NAME, keyGenerator = "keyGenerator")
|
||||
@CacheConfig(cacheNames = ZFileCacheConfiguration.CACHE_NAME, keyGenerator = "keyGenerator")
|
||||
public interface FileService {
|
||||
|
||||
@Cacheable
|
||||
List<FileItem> fileList(String path) throws Exception;
|
||||
List<FileItemDTO> fileList(String path) throws Exception;
|
||||
|
||||
@Cacheable
|
||||
String getDownloadUrl(String path) throws Exception;
|
||||
|
||||
/**
|
||||
* 获取文件内容.
|
||||
*/
|
||||
default String getTextContent(String url) throws Exception {
|
||||
RestTemplate restTemplate = SpringContextHolder.getBean(RestTemplate.class);
|
||||
String result = restTemplate.getForObject(url, String.class);
|
||||
return result == null ? "" : result;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
default void initMethod() throws Exception {}
|
||||
default void init() {}
|
||||
|
||||
/**
|
||||
* 清除缓存.
|
||||
*/
|
||||
@CacheEvict(allEntries = true)
|
||||
default void clearCache() throws Exception {
|
||||
}
|
||||
default void clearCache() {}
|
||||
|
||||
/**
|
||||
* 获取图片信息
|
||||
* @param url 图片 URL
|
||||
* @return 图片的信息, 宽、高
|
||||
*/
|
||||
@Cacheable
|
||||
default ImageInfo getImageInfo(String url) throws Exception {
|
||||
url = URLUtil.decode(url);
|
||||
URL urlObject = new URL(url);
|
||||
String originPath = urlObject.getPath();
|
||||
url = url.replace(originPath, URLUtil.encode(originPath));
|
||||
InputStream inputStream = new URL(url).openStream();
|
||||
BufferedImage sourceImg = ImageIO.read(inputStream);
|
||||
return new ImageInfo(sourceImg.getWidth(), sourceImg.getHeight());
|
||||
}
|
||||
default List<FileItemDTO> search(String name) throws Exception {
|
||||
List<FileItemDTO> result = new ArrayList<>();
|
||||
|
||||
default AudioInfo getAudioInfo(String url) throws Exception {
|
||||
String query = new URL(URLUtil.decode(url)).getQuery();
|
||||
|
||||
if (query != null) {
|
||||
url = url.replace(query, URLUtil.encode(query));
|
||||
}
|
||||
|
||||
File file = new File(System.getProperty("user.home") + "/zfile/tmp/audio/" + UUID.fastUUID());
|
||||
FileUtil.mkParentDirs(file);
|
||||
HttpUtil.downloadFile(url, file);
|
||||
AudioInfo audioInfo = AudioHelper.parseAudioInfo(file);
|
||||
audioInfo.setSrc(url);
|
||||
file.deleteOnExit();
|
||||
return audioInfo;
|
||||
}
|
||||
|
||||
|
||||
default List<FileItem> search(String name) throws Exception {
|
||||
List<FileItem> result = new ArrayList<>();
|
||||
|
||||
List<FileItem> fileItemList = selectAllFileList();
|
||||
for (FileItem fileItem : fileItemList) {
|
||||
if (fileItem.getName().contains(name)) {
|
||||
result.add(fileItem);
|
||||
List<FileItemDTO> fileItemList = selectAllFileList();
|
||||
for (FileItemDTO fileItemDTO : fileItemList) {
|
||||
if (fileItemDTO.getName().contains(name)) {
|
||||
result.add(fileItemDTO);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
default List<FileItem> selectAllFileList() throws Exception {
|
||||
List<FileItem> result = new ArrayList<>();
|
||||
default List<FileItemDTO> selectAllFileList() throws Exception {
|
||||
List<FileItemDTO> result = new ArrayList<>();
|
||||
|
||||
String path = "/";
|
||||
|
||||
FileService currentFileService = (FileService) AopContext.currentProxy();
|
||||
List<FileItem> fileItemList = currentFileService.fileList(path);
|
||||
ArrayDeque<FileItem> queue = new ArrayDeque<>(fileItemList);
|
||||
List<FileItemDTO> fileItemList = currentFileService.fileList(path);
|
||||
ArrayDeque<FileItemDTO> queue = new ArrayDeque<>(fileItemList);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
FileItem fileItem = queue.pop();
|
||||
result.add(fileItem);
|
||||
if (fileItem.getType() == FileTypeEnum.FOLDER) {
|
||||
String filePath = StringUtils.removeDuplicateSeparator("/" + fileItem.getPath() + "/" + fileItem.getName() + "/");
|
||||
FileItemDTO fileItemDTO = queue.pop();
|
||||
result.add(fileItemDTO);
|
||||
if (fileItemDTO.getType() == FileTypeEnum.FOLDER) {
|
||||
String filePath = StringUtils.removeDuplicateSeparator("/" + fileItemDTO.getPath() + "/" + fileItemDTO.getName() + "/");
|
||||
queue.addAll(currentFileService.fileList(filePath));
|
||||
}
|
||||
}
|
||||
@@ -125,4 +68,6 @@ public interface FileService {
|
||||
}
|
||||
|
||||
StorageTypeEnum getStorageTypeEnum();
|
||||
|
||||
boolean getIsInitialized();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.common.service;
|
||||
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.repository.StorageConfigRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -16,15 +16,20 @@ public class StorageConfigService {
|
||||
@Resource
|
||||
private StorageConfigRepository storageConfigRepository;
|
||||
|
||||
private List<StorageConfig> selectStorageConfigByType(StorageTypeEnum storageTypeEnum) {
|
||||
return storageConfigRepository.findByType(storageTypeEnum);
|
||||
public List<StorageConfig> selectStorageConfigByType(StorageTypeEnum storageTypeEnum) {
|
||||
return storageConfigRepository.findByTypeOrderById(storageTypeEnum);
|
||||
}
|
||||
|
||||
public Map<String, StorageConfig> selectStorageConfigMapByKey(StorageTypeEnum storageTypeEnum) {
|
||||
Map<String, StorageConfig> map = new HashMap<>();
|
||||
Map<String, StorageConfig> map = new HashMap<>(24);
|
||||
for (StorageConfig storageConfig : selectStorageConfigByType(storageTypeEnum)) {
|
||||
map.put(storageConfig.getKey(), storageConfig);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public void updateStorageConfig(List<StorageConfig> storageConfigList) {
|
||||
storageConfigRepository.saveAll(storageConfigList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
118
src/main/java/im/zhaojun/common/service/SystemConfigService.java
Normal file
118
src/main/java/im/zhaojun/common/service/SystemConfigService.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package im.zhaojun.common.service;
|
||||
|
||||
import im.zhaojun.common.config.StorageTypeFactory;
|
||||
import im.zhaojun.common.model.SystemConfig;
|
||||
import im.zhaojun.common.model.SystemConfigDTO;
|
||||
import im.zhaojun.common.model.constant.SystemConfigConstant;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.repository.SystemConfigRepository;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SystemConfigService {
|
||||
|
||||
@Resource
|
||||
private SystemConfigRepository systemConfigRepository;
|
||||
|
||||
public SystemConfigDTO getSystemConfig() {
|
||||
SystemConfigDTO systemConfigDTO = new SystemConfigDTO();
|
||||
List<SystemConfig> systemConfigList = systemConfigRepository.findAll();
|
||||
|
||||
for (SystemConfig systemConfig : systemConfigList) {
|
||||
switch (systemConfig.getKey()) {
|
||||
case SystemConfigConstant.SITE_NAME:
|
||||
systemConfigDTO.setSiteName(systemConfig.getValue());
|
||||
break;
|
||||
case SystemConfigConstant.INFO_ENABLE:
|
||||
systemConfigDTO.setInfoEnable("true".equals(systemConfig.getValue()));
|
||||
break;
|
||||
case SystemConfigConstant.SEARCH_ENABLE:
|
||||
systemConfigDTO.setSearchEnable("true".equals(systemConfig.getValue()));
|
||||
break;
|
||||
case SystemConfigConstant.SEARCH_IGNORE_CASE:
|
||||
systemConfigDTO.setSearchIgnoreCase("true".equals(systemConfig.getValue()));
|
||||
break;
|
||||
case SystemConfigConstant.STORAGE_STRATEGY:
|
||||
String value = systemConfig.getValue();
|
||||
systemConfigDTO.setStorageStrategy(StorageTypeEnum.getEnum(value));
|
||||
break;
|
||||
case SystemConfigConstant.USERNAME:
|
||||
systemConfigDTO.setUsername(systemConfig.getValue());
|
||||
break;
|
||||
case SystemConfigConstant.PASSWORD:
|
||||
systemConfigDTO.setPassword(systemConfig.getValue());
|
||||
break;
|
||||
default:break;
|
||||
}
|
||||
}
|
||||
|
||||
return systemConfigDTO;
|
||||
}
|
||||
|
||||
public void updateSystemConfig(SystemConfigDTO systemConfigDTO) {
|
||||
List<SystemConfig> systemConfigList = new ArrayList<>();
|
||||
|
||||
SystemConfig systemConfig = systemConfigRepository.findByKey(SystemConfigConstant.SITE_NAME);
|
||||
systemConfig.setValue(systemConfigDTO.getSiteName());
|
||||
systemConfigList.add(systemConfig);
|
||||
|
||||
SystemConfig infoEnableSystemConfig = systemConfigRepository.findByKey(SystemConfigConstant.INFO_ENABLE);
|
||||
infoEnableSystemConfig.setValue(systemConfigDTO.getInfoEnable() ? "true" : "false");
|
||||
systemConfigList.add(infoEnableSystemConfig);
|
||||
|
||||
SystemConfig searchEnableSystemConfig = systemConfigRepository.findByKey(SystemConfigConstant.SEARCH_ENABLE);
|
||||
searchEnableSystemConfig.setValue(systemConfigDTO.getSearchEnable() ? "true" : "false");
|
||||
systemConfigList.add(searchEnableSystemConfig);
|
||||
|
||||
SystemConfig searchIgnoreCaseSystemConfig = systemConfigRepository.findByKey(SystemConfigConstant.SEARCH_IGNORE_CASE);
|
||||
searchIgnoreCaseSystemConfig.setValue(systemConfigDTO.getSearchIgnoreCase() ? "true" : "false");
|
||||
systemConfigList.add(searchIgnoreCaseSystemConfig);
|
||||
|
||||
SystemConfig storageStrategySystemConfig = systemConfigRepository.findByKey(SystemConfigConstant.STORAGE_STRATEGY);
|
||||
storageStrategySystemConfig.setValue(systemConfigDTO.getStorageStrategy().getKey());
|
||||
systemConfigList.add(storageStrategySystemConfig);
|
||||
|
||||
if (!StringUtils.isNullOrEmpty(systemConfigDTO.getUsername())) {
|
||||
SystemConfig usernameSystemConfig = systemConfigRepository.findByKey(SystemConfigConstant.USERNAME);
|
||||
usernameSystemConfig.setValue(systemConfigDTO.getUsername());
|
||||
systemConfigList.add(usernameSystemConfig);
|
||||
}
|
||||
|
||||
if (!StringUtils.isNullOrEmpty(systemConfigDTO.getPassword())) {
|
||||
SystemConfig passwordSystemConfig = systemConfigRepository.findByKey(SystemConfigConstant.PASSWORD);
|
||||
passwordSystemConfig.setValue(systemConfigDTO.getPassword());
|
||||
systemConfigList.add(passwordSystemConfig);
|
||||
}
|
||||
|
||||
systemConfigRepository.saveAll(systemConfigList);
|
||||
}
|
||||
|
||||
public void updateUsernameAndPwd(String username, String password) {
|
||||
SystemConfig usernameConfig = systemConfigRepository.findByKey(SystemConfigConstant.USERNAME);
|
||||
usernameConfig.setValue(username);
|
||||
systemConfigRepository.save(usernameConfig);
|
||||
|
||||
password = new BCryptPasswordEncoder().encode(password);
|
||||
SystemConfig systemConfig = systemConfigRepository.findByKey(SystemConfigConstant.PASSWORD);
|
||||
systemConfig.setValue(password);
|
||||
|
||||
systemConfigRepository.save(systemConfig);
|
||||
}
|
||||
|
||||
public FileService getCurrentFileService() {
|
||||
StorageTypeEnum storageStrategy = getCurrentStorageStrategy();
|
||||
return StorageTypeFactory.getStorageTypeService(storageStrategy);
|
||||
}
|
||||
|
||||
public StorageTypeEnum getCurrentStorageStrategy() {
|
||||
SystemConfigDTO systemConfigDTO = getSystemConfig();
|
||||
return systemConfigDTO.getStorageStrategy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package im.zhaojun.common.service;
|
||||
|
||||
import im.zhaojun.common.constant.ZfileConstant;
|
||||
import im.zhaojun.common.model.FileItem;
|
||||
import im.zhaojun.common.model.SiteConfig;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import im.zhaojun.common.model.constant.ZFileConstant;
|
||||
import im.zhaojun.common.model.dto.FileItemDTO;
|
||||
import im.zhaojun.common.model.dto.SiteConfigDTO;
|
||||
import im.zhaojun.common.util.HttpUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -13,27 +13,26 @@ import java.util.List;
|
||||
public class SystemService {
|
||||
|
||||
@Resource
|
||||
private ViewConfigService viewConfigService;
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
/**
|
||||
* 构建指定路径下标题, 页头, 页尾
|
||||
* @param path 路径
|
||||
*/
|
||||
public SiteConfig getConfig(String path) throws Exception {
|
||||
public SiteConfigDTO getConfig(String path) throws Exception {
|
||||
|
||||
SiteConfig siteConfig = new SiteConfig();
|
||||
FileService fileService = viewConfigService.getCurrentFileService();
|
||||
SiteConfigDTO siteConfigDTO = new SiteConfigDTO();
|
||||
FileService fileService = systemConfigService.getCurrentFileService();
|
||||
|
||||
List<FileItem> fileItemList = fileService.fileList(path);
|
||||
path = StringUtils.removeLastSeparator(path);
|
||||
for (FileItem fileItem : fileItemList) {
|
||||
if (ZfileConstant.FOOTER_FILE_NAME.equalsIgnoreCase(fileItem.getName())) {
|
||||
siteConfig.setFooter(fileService.getTextContent(fileItem.getUrl()));
|
||||
} else if (ZfileConstant.HEADER_FILE_NAME.equalsIgnoreCase(fileItem.getName())) {
|
||||
siteConfig.setHeader(fileService.getTextContent(fileItem.getUrl()));
|
||||
List<FileItemDTO> fileItemList = fileService.fileList(path);
|
||||
for (FileItemDTO fileItemDTO : fileItemList) {
|
||||
if (ZFileConstant.FOOTER_FILE_NAME.equalsIgnoreCase(fileItemDTO.getName())) {
|
||||
siteConfigDTO.setFooter(HttpUtil.getTextContent(fileItemDTO.getUrl()));
|
||||
} else if (ZFileConstant.HEADER_FILE_NAME.equalsIgnoreCase(fileItemDTO.getName())) {
|
||||
siteConfigDTO.setHeader(HttpUtil.getTextContent(fileItemDTO.getUrl()));
|
||||
}
|
||||
}
|
||||
return siteConfig;
|
||||
return siteConfigDTO;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package im.zhaojun.common.service;
|
||||
|
||||
import im.zhaojun.common.config.StorageTypeFactory;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.ViewConfig;
|
||||
import im.zhaojun.common.repository.ViewConfigRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Service
|
||||
public class ViewConfigService {
|
||||
|
||||
@Resource
|
||||
private ViewConfigRepository viewConfigRepository;
|
||||
|
||||
public ViewConfig getViewConfig() {
|
||||
return viewConfigRepository.findFirstBy();
|
||||
}
|
||||
|
||||
public FileService getCurrentFileService() {
|
||||
ViewConfig viewConfig = getViewConfig();
|
||||
StorageTypeEnum storageStrategy = viewConfig.getStorageStrategy();
|
||||
return StorageTypeFactory.getStorageTypeService(storageStrategy);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +1,49 @@
|
||||
package im.zhaojun.common.util;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.mpatric.mp3agic.*;
|
||||
import im.zhaojun.common.model.AudioInfo;
|
||||
import im.zhaojun.common.model.constant.ZFileConstant;
|
||||
import im.zhaojun.common.model.dto.AudioInfoDTO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
|
||||
/**
|
||||
* 音频解析工具类
|
||||
*/
|
||||
public class AudioHelper {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AudioHelper.class);
|
||||
|
||||
public static AudioInfo parseAudioInfo(File file) throws InvalidDataException, IOException, UnsupportedTagException {
|
||||
AudioInfo audioInfo = new AudioInfo();
|
||||
audioInfo.setTitle("未知歌曲");
|
||||
audioInfo.setArtist("未知");
|
||||
audioInfo.setCover("/shikwasa/audio.png");
|
||||
public static AudioInfoDTO getAudioInfo(String url) throws Exception {
|
||||
String query = new URL(URLUtil.decode(url)).getQuery();
|
||||
|
||||
if (query != null) {
|
||||
url = url.replace(query, URLUtil.encode(query));
|
||||
}
|
||||
|
||||
File file = new File(ZFileConstant.USER_HOME + ZFileConstant.AUDIO_TMP_PATH + UUID.fastUUID());
|
||||
FileUtil.mkParentDirs(file);
|
||||
HttpUtil.downloadFile(url, file);
|
||||
AudioInfoDTO audioInfoDTO = parseAudioInfo(file);
|
||||
audioInfoDTO.setSrc(url);
|
||||
file.deleteOnExit();
|
||||
return audioInfoDTO;
|
||||
}
|
||||
|
||||
private static AudioInfoDTO parseAudioInfo(File file) throws IOException, UnsupportedTagException {
|
||||
AudioInfoDTO audioInfoDTO = new AudioInfoDTO();
|
||||
audioInfoDTO.setTitle("未知歌曲");
|
||||
audioInfoDTO.setArtist("未知");
|
||||
audioInfoDTO.setCover("/shikwasa/audio.png");
|
||||
|
||||
Mp3File mp3File = null;
|
||||
try {
|
||||
@@ -29,7 +55,7 @@ public class AudioHelper {
|
||||
}
|
||||
|
||||
if (mp3File == null) {
|
||||
return audioInfo;
|
||||
return audioInfoDTO;
|
||||
}
|
||||
|
||||
ID3v1 audioTag = null;
|
||||
@@ -38,16 +64,16 @@ public class AudioHelper {
|
||||
ID3v2 id3v2Tag = mp3File.getId3v2Tag();
|
||||
byte[] albumImage = id3v2Tag.getAlbumImage();
|
||||
if (albumImage != null) {
|
||||
audioInfo.setCover("data:" + id3v2Tag.getAlbumImageMimeType() + ";base64," + Base64.encode(albumImage));
|
||||
audioInfoDTO.setCover("data:" + id3v2Tag.getAlbumImageMimeType() + ";base64," + Base64.encode(albumImage));
|
||||
}
|
||||
audioTag = id3v2Tag;
|
||||
}
|
||||
|
||||
if (audioTag != null) {
|
||||
audioInfo.setTitle(audioTag.getTitle());
|
||||
audioInfo.setArtist(audioTag.getArtist());
|
||||
audioInfoDTO.setTitle(audioTag.getTitle());
|
||||
audioInfoDTO.setArtist(audioTag.getArtist());
|
||||
}
|
||||
|
||||
return audioInfo;
|
||||
return audioInfoDTO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package im.zhaojun.common.util;
|
||||
|
||||
import im.zhaojun.common.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.model.FileItem;
|
||||
import im.zhaojun.common.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.model.dto.FileItemDTO;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.Comparator;
|
||||
* - 默认排序为升序
|
||||
* - 按名称排序不区分大小写
|
||||
*/
|
||||
public class FileComparator implements Comparator<FileItem> {
|
||||
public class FileComparator implements Comparator<FileItemDTO> {
|
||||
|
||||
private String sortBy;
|
||||
private String order;
|
||||
@@ -24,7 +24,7 @@ public class FileComparator implements Comparator<FileItem> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(FileItem o1, FileItem o2) {
|
||||
public int compare(FileItemDTO o1, FileItemDTO o2) {
|
||||
FileTypeEnum o1Type = o1.getType();
|
||||
FileTypeEnum o2Type = o2.getType();
|
||||
|
||||
|
||||
13
src/main/java/im/zhaojun/common/util/HttpUtil.java
Normal file
13
src/main/java/im/zhaojun/common/util/HttpUtil.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package im.zhaojun.common.util;
|
||||
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
public class HttpUtil {
|
||||
|
||||
public static String getTextContent(String url) {
|
||||
RestTemplate restTemplate = SpringContextHolder.getBean(RestTemplate.class);
|
||||
String result = restTemplate.getForObject(url, String.class);
|
||||
return result == null ? "" : result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -54,13 +54,13 @@ public class SpringContextHolder implements ApplicationContextAware, DisposableB
|
||||
* 实现 DisposableBean 接口, 在 Context 关闭时清理静态变量.
|
||||
*/
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
public void destroy() {
|
||||
SpringContextHolder.clearHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
SpringContextHolder.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
package im.zhaojun.common.util;
|
||||
|
||||
import im.zhaojun.common.config.StorageTypeFactory;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.ViewConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.ViewConfigService;
|
||||
import im.zhaojun.common.service.FileAsyncCacheService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
@@ -20,26 +16,17 @@ import javax.annotation.Resource;
|
||||
public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(StartupListener.class);
|
||||
|
||||
@Resource
|
||||
private ViewConfigService viewConfigService;
|
||||
private FileAsyncCacheService fileAsyncCacheService;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
ViewConfig viewConfig = viewConfigService.getViewConfig();
|
||||
StorageTypeEnum storageStrategy = viewConfig.getStorageStrategy();
|
||||
FileService fileService = StorageTypeFactory.getStorageTypeService(storageStrategy);
|
||||
new Thread(() -> {
|
||||
log.info("缓存 {} 所有文件开始", storageStrategy.getDescription());
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
fileService.selectAllFileList();
|
||||
} catch (Exception e) {
|
||||
log.error("缓存所有文件失败", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
long endTime = System.currentTimeMillis();
|
||||
log.info("缓存 {} 所有文件结束, 用时: {} 秒", storageStrategy.getDescription(), ( (endTime - startTime) / 1000 ));
|
||||
}).start();
|
||||
try {
|
||||
fileAsyncCacheService.cacheGlobalFile();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("缓存异常.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class StringUtils {
|
||||
return path;
|
||||
}
|
||||
|
||||
public static String concatURL(String path, String name) {
|
||||
public static String concatUrl(String path, String name) {
|
||||
return removeDuplicateSeparator("/" + path + "/" + name);
|
||||
}
|
||||
|
||||
@@ -71,4 +71,9 @@ public class StringUtils {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static boolean isNullOrEmpty(String s) {
|
||||
return s == null || "".equals(s);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package im.zhaojun.ftp.service;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.extra.ftp.Ftp;
|
||||
import im.zhaojun.common.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.FileItem;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import org.apache.commons.net.ftp.FTPFile;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class FtpService implements FileService {
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
private static final String HOST_KEY = "host";
|
||||
|
||||
private static final String PORT_KEY = "port";
|
||||
|
||||
private static final String USERNAME_KEY = "username";
|
||||
|
||||
private static final String PASSWORD_KEY = "password";
|
||||
|
||||
private static final String DOMAIN_KEY = "domain";
|
||||
|
||||
private Ftp ftp;
|
||||
|
||||
private String domain;
|
||||
|
||||
@Override
|
||||
public void initMethod() {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.FTP);
|
||||
String host = stringStorageConfigMap.get(HOST_KEY).getValue();
|
||||
String port = stringStorageConfigMap.get(PORT_KEY).getValue();
|
||||
String username = stringStorageConfigMap.get(USERNAME_KEY).getValue();
|
||||
String password = stringStorageConfigMap.get(PASSWORD_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
|
||||
ftp = new Ftp(host, Integer.parseInt(port), username, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItem> fileList(String path) {
|
||||
FTPFile[] ftpFiles = ftp.lsFiles(path);
|
||||
|
||||
List<FileItem> fileItemList = new ArrayList<>();
|
||||
|
||||
for (FTPFile ftpFile : ftpFiles) {
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setName(ftpFile.getName());
|
||||
fileItem.setSize(ftpFile.getSize());
|
||||
fileItem.setTime(ftpFile.getTimestamp().getTime());
|
||||
fileItem.setType(ftpFile.isDirectory() ? FileTypeEnum.FOLDER : FileTypeEnum.FILE);
|
||||
fileItem.setPath(path);
|
||||
fileItemList.add(fileItem);
|
||||
}
|
||||
return fileItemList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl(String path) {
|
||||
return URLUtil.complateUrl(domain, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.FTP;
|
||||
}
|
||||
}
|
||||
100
src/main/java/im/zhaojun/ftp/service/FtpServiceImpl.java
Normal file
100
src/main/java/im/zhaojun/ftp/service/FtpServiceImpl.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package im.zhaojun.ftp.service;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.extra.ftp.Ftp;
|
||||
import im.zhaojun.common.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.dto.FileItemDTO;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.apache.commons.net.ftp.FTPFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class FtpServiceImpl implements FileService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FtpServiceImpl.class);
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
private static final String HOST_KEY = "host";
|
||||
|
||||
private static final String PORT_KEY = "port";
|
||||
|
||||
private static final String USERNAME_KEY = "username";
|
||||
|
||||
private static final String PASSWORD_KEY = "password";
|
||||
|
||||
private static final String DOMAIN_KEY = "domain";
|
||||
|
||||
private Ftp ftp;
|
||||
|
||||
private String domain;
|
||||
|
||||
private boolean isInitialized;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
try {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.FTP);
|
||||
String host = stringStorageConfigMap.get(HOST_KEY).getValue();
|
||||
String port = stringStorageConfigMap.get(PORT_KEY).getValue();
|
||||
String username = stringStorageConfigMap.get(USERNAME_KEY).getValue();
|
||||
String password = stringStorageConfigMap.get(PASSWORD_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
|
||||
ftp = new Ftp(host, Integer.parseInt(port), username, password);
|
||||
isInitialized = true;
|
||||
} catch (Exception e) {
|
||||
log.debug(StorageTypeEnum.FTP.getDescription() + "初始化异常, 已跳过");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItemDTO> fileList(String path) {
|
||||
FTPFile[] ftpFiles = ftp.lsFiles(path);
|
||||
|
||||
List<FileItemDTO> fileItemList = new ArrayList<>();
|
||||
|
||||
for (FTPFile ftpFile : ftpFiles) {
|
||||
FileItemDTO fileItemDTO = new FileItemDTO();
|
||||
fileItemDTO.setName(ftpFile.getName());
|
||||
fileItemDTO.setSize(ftpFile.getSize());
|
||||
fileItemDTO.setTime(ftpFile.getTimestamp().getTime());
|
||||
fileItemDTO.setType(ftpFile.isDirectory() ? FileTypeEnum.FOLDER : FileTypeEnum.FILE);
|
||||
fileItemDTO.setPath(path);
|
||||
if (ftpFile.isFile()) {
|
||||
fileItemDTO.setUrl(getDownloadUrl(StringUtils.concatUrl(path, fileItemDTO.getName())));
|
||||
}
|
||||
fileItemList.add(fileItemDTO);
|
||||
}
|
||||
return fileItemList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl(String path) {
|
||||
return URLUtil.complateUrl(domain, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.FTP;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean getIsInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,15 @@ package im.zhaojun.huawei.service;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.obs.services.ObsClient;
|
||||
import com.obs.services.model.*;
|
||||
import im.zhaojun.common.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.FileItem;
|
||||
import im.zhaojun.common.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.dto.FileItemDTO;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -20,7 +22,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class HuaweiService implements FileService {
|
||||
public class HuaweiServiceImpl implements FileService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HuaweiServiceImpl.class);
|
||||
|
||||
private String bucketName;
|
||||
|
||||
@@ -44,24 +48,31 @@ public class HuaweiService implements FileService {
|
||||
|
||||
private ObsClient obsClient;
|
||||
|
||||
@Override
|
||||
public void initMethod() {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.HUAWEI);
|
||||
String accessKey = stringStorageConfigMap.get(ACCESS_KEY).getValue();
|
||||
String secretKey = stringStorageConfigMap.get(SECRET_KEY).getValue();
|
||||
String endPoint = stringStorageConfigMap.get(ENDPOINT_KEY).getValue();
|
||||
private boolean isInitialized;
|
||||
|
||||
bucketName = stringStorageConfigMap.get(BUCKET_NAME_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
obsClient = new ObsClient(accessKey, secretKey, endPoint);
|
||||
@Override
|
||||
public void init() {
|
||||
try {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.HUAWEI);
|
||||
String accessKey = stringStorageConfigMap.get(ACCESS_KEY).getValue();
|
||||
String secretKey = stringStorageConfigMap.get(SECRET_KEY).getValue();
|
||||
String endPoint = stringStorageConfigMap.get(ENDPOINT_KEY).getValue();
|
||||
|
||||
bucketName = stringStorageConfigMap.get(BUCKET_NAME_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
obsClient = new ObsClient(accessKey, secretKey, endPoint);
|
||||
isInitialized = true;
|
||||
} catch (Exception e) {
|
||||
log.debug(StorageTypeEnum.HUAWEI.getDescription() + "初始化异常, 已跳过");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItem> fileList(String path) {
|
||||
public List<FileItemDTO> fileList(String path) throws Exception {
|
||||
path = StringUtils.removeFirstSeparator(path);
|
||||
|
||||
List<FileItem> fileItemList = new ArrayList<>();
|
||||
List<FileItemDTO> fileItemList = new ArrayList<>();
|
||||
|
||||
ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
|
||||
listObjectsRequest.setBucketName(bucketName);
|
||||
@@ -74,21 +85,22 @@ public class HuaweiService implements FileService {
|
||||
String fileName = object.getObjectKey();
|
||||
ObjectMetadata metadata = object.getMetadata();
|
||||
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setName(fileName.substring(path.length()));
|
||||
fileItem.setSize(metadata.getContentLength());
|
||||
fileItem.setTime(metadata.getLastModified());
|
||||
fileItem.setType(FileTypeEnum.FILE);
|
||||
fileItem.setPath(path);
|
||||
fileItemList.add(fileItem);
|
||||
FileItemDTO fileItemDTO = new FileItemDTO();
|
||||
fileItemDTO.setName(fileName.substring(path.length()));
|
||||
fileItemDTO.setSize(metadata.getContentLength());
|
||||
fileItemDTO.setTime(metadata.getLastModified());
|
||||
fileItemDTO.setType(FileTypeEnum.FILE);
|
||||
fileItemDTO.setPath(path);
|
||||
fileItemDTO.setUrl(getDownloadUrl(StringUtils.concatUrl(path, fileItemDTO.getName())));
|
||||
fileItemList.add(fileItemDTO);
|
||||
}
|
||||
|
||||
for (String commonPrefix : objectListing.getCommonPrefixes()) {
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setName(commonPrefix.substring(0, commonPrefix.length() - 1));
|
||||
fileItem.setType(FileTypeEnum.FOLDER);
|
||||
fileItem.setPath(path);
|
||||
fileItemList.add(fileItem);
|
||||
FileItemDTO fileItemDTO = new FileItemDTO();
|
||||
fileItemDTO.setName(commonPrefix.substring(0, commonPrefix.length() - 1));
|
||||
fileItemDTO.setType(FileTypeEnum.FOLDER);
|
||||
fileItemDTO.setPath(path);
|
||||
fileItemList.add(fileItemDTO);
|
||||
}
|
||||
|
||||
return fileItemList;
|
||||
@@ -109,4 +121,12 @@ public class HuaweiService implements FileService {
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.HUAWEI;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean getIsInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,16 +2,19 @@ package im.zhaojun.local.controller;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import im.zhaojun.local.service.LocalService;
|
||||
import im.zhaojun.local.service.LocalServiceImpl;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -21,12 +24,18 @@ import java.util.Date;
|
||||
public class LocalController {
|
||||
|
||||
@Resource
|
||||
private LocalService localService;
|
||||
private LocalServiceImpl localServiceImpl;
|
||||
|
||||
@GetMapping("/local-download")
|
||||
@GetMapping("/file/**")
|
||||
@ResponseBody
|
||||
public ResponseEntity<FileSystemResource> downAttachment(String fileName) throws IOException {
|
||||
return export(new File(StringUtils.concatPath(localService.getFilePath(), URLUtil.decode(fileName))));
|
||||
public ResponseEntity<FileSystemResource> downAttachment(final HttpServletRequest request) throws IOException {
|
||||
String path = (String) request.getAttribute(
|
||||
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
|
||||
String bestMatchPattern = (String ) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
|
||||
AntPathMatcher apm = new AntPathMatcher();
|
||||
String filePath = apm.extractPathWithinPattern(bestMatchPattern, path);
|
||||
|
||||
return export(new File(StringUtils.concatPath(localServiceImpl.getFilePath(), URLUtil.decode(filePath))));
|
||||
}
|
||||
|
||||
private ResponseEntity<FileSystemResource> export(File file) throws IOException {
|
||||
@@ -36,7 +45,6 @@ public class LocalController {
|
||||
MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
// 如果
|
||||
if (fileMimeType == null || "".equals(fileMimeType)) {
|
||||
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
headers.add("Content-Disposition", "attachment; filename=" + file.getName());
|
||||
@@ -55,5 +63,4 @@ public class LocalController {
|
||||
.body(new FileSystemResource(file));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
package im.zhaojun.local.service;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import im.zhaojun.common.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.FileItem;
|
||||
import im.zhaojun.common.model.ImageInfo;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class LocalService implements FileService {
|
||||
|
||||
private static final String FILE_PATH_KEY = "filePath";
|
||||
|
||||
@Value("${server.port}")
|
||||
private String port;
|
||||
|
||||
@Value("${server.servlet.context-path}")
|
||||
private String contextPath;
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
private String filePath;
|
||||
|
||||
@Override
|
||||
public void initMethod() {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.LOCAL);
|
||||
filePath = stringStorageConfigMap.get(FILE_PATH_KEY).getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItem> fileList(String path) throws Exception {
|
||||
List<FileItem> fileItemList = new ArrayList<>();
|
||||
|
||||
String fullPath = StringUtils.concatPath(filePath, path);
|
||||
|
||||
File file = new File(fullPath);
|
||||
File[] files = file.listFiles();
|
||||
|
||||
if (files == null) {
|
||||
return fileItemList;
|
||||
}
|
||||
for (File f : files) {
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setType(f.isDirectory() ? FileTypeEnum.FOLDER : FileTypeEnum.FILE);
|
||||
fileItem.setTime(new Date(f.lastModified()));
|
||||
fileItem.setSize(f.length());
|
||||
fileItem.setName(f.getName());
|
||||
fileItem.setPath(path);
|
||||
if (!f.isDirectory()) {
|
||||
fileItem.setUrl(getDownloadUrl(StringUtils.concatURL(path, f.getName())));
|
||||
}
|
||||
fileItemList.add(fileItem);
|
||||
}
|
||||
|
||||
return fileItemList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl(String path) throws Exception {
|
||||
InetAddress localHost = Inet4Address.getLocalHost();
|
||||
String host = localHost.getHostAddress();
|
||||
return StringUtils.concatPath( "http://" + host + ":" + port + contextPath, "local-download?fileName=" + path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageInfo getImageInfo(String url) throws Exception {
|
||||
String query = new URL(URLUtil.decode(url)).getQuery();
|
||||
url = url.replace(query, URLUtil.encode(query));
|
||||
InputStream inputStream = new URL(url).openStream();
|
||||
BufferedImage sourceImg = ImageIO.read(inputStream);
|
||||
return new ImageInfo(sourceImg.getWidth(), sourceImg.getHeight());
|
||||
}
|
||||
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.LOCAL;
|
||||
}
|
||||
}
|
||||
111
src/main/java/im/zhaojun/local/service/LocalServiceImpl.java
Normal file
111
src/main/java/im/zhaojun/local/service/LocalServiceImpl.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package im.zhaojun.local.service;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import im.zhaojun.common.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.dto.FileItemDTO;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class LocalServiceImpl implements FileService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LocalServiceImpl.class);
|
||||
|
||||
private static final String FILE_PATH_KEY = "filePath";
|
||||
|
||||
@Value("${server.port}")
|
||||
private String port;
|
||||
|
||||
@Value("${server.servlet.context-path}")
|
||||
private String contextPath;
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
private String filePath;
|
||||
|
||||
private boolean isInitialized;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
try {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.LOCAL);
|
||||
filePath = stringStorageConfigMap.get(FILE_PATH_KEY).getValue();
|
||||
isInitialized = true;
|
||||
} catch (Exception e) {
|
||||
log.debug(StorageTypeEnum.LOCAL.getDescription() + "初始化异常, 已跳过");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItemDTO> fileList(String path) throws Exception {
|
||||
List<FileItemDTO> fileItemList = new ArrayList<>();
|
||||
|
||||
String fullPath = StringUtils.concatPath(filePath, path);
|
||||
|
||||
File file = new File(fullPath);
|
||||
File[] files = file.listFiles();
|
||||
|
||||
if (files == null) {
|
||||
return fileItemList;
|
||||
}
|
||||
for (File f : files) {
|
||||
FileItemDTO fileItemDTO = new FileItemDTO();
|
||||
fileItemDTO.setType(f.isDirectory() ? FileTypeEnum.FOLDER : FileTypeEnum.FILE);
|
||||
fileItemDTO.setTime(new Date(f.lastModified()));
|
||||
fileItemDTO.setSize(f.length());
|
||||
fileItemDTO.setName(f.getName());
|
||||
fileItemDTO.setPath(path);
|
||||
if (f.isFile()) {
|
||||
fileItemDTO.setUrl(getDownloadUrl(StringUtils.concatUrl(path, f.getName())));
|
||||
}
|
||||
fileItemList.add(fileItemDTO);
|
||||
}
|
||||
|
||||
return fileItemList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl(String path) throws Exception {
|
||||
InetAddress localHost = Inet4Address.getLocalHost();
|
||||
String host = localHost.getHostAddress();
|
||||
return StringUtils.concatPath( "http://" + host + ":" + port + contextPath, "file" + path);
|
||||
}
|
||||
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.LOCAL;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean getIsInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
}
|
||||
35
src/main/java/im/zhaojun/onedrive/config/OneDriveConfig.java
Normal file
35
src/main/java/im/zhaojun/onedrive/config/OneDriveConfig.java
Normal file
@@ -0,0 +1,35 @@
|
||||
// package im.zhaojun.onedrive.config;
|
||||
//
|
||||
// import org.nuxeo.onedrive.client.*;
|
||||
// import org.springframework.context.annotation.Configuration;
|
||||
//
|
||||
// @Configuration
|
||||
// public class OneDriveConfig {
|
||||
//
|
||||
//
|
||||
// public void a () {
|
||||
// OneDriveAPI api = new OneDriveBasicAPI("YOUR_ACCESS_TOKEN");
|
||||
//
|
||||
// OneDriveFolder folder = new OneDriveFolder(api, "FOLDER_ID");
|
||||
// OneDriveFile file = new OneDriveFile(api, "FILE_ID");
|
||||
// }
|
||||
//
|
||||
// public static void main(String[] args) throws OneDriveAPIException {
|
||||
// OneDriveBasicAPI api = new OneDriveBasicAPI("EwAgA61DBAAUcSSzoTJJsy+XrnQXgAKO5cj4yc8AAQ0ZknDUY8YnwB9aJv7vA9YjiRAVMnKc+rG11fSXLZRAA8Q/CgJaz+OkRN60vaLDfp6KxbmVlob6kxeD/peOUI2eHtk0055Q2+n057tlyVAvGIFl9dvqkItoAthjmybcSkKBZS5h1meWxQ5IOvzSVrdgCKL0NOtTxfh33ZUDsYjvSid6NOX4Bs+pRjvZhQkvqEfGt8KlOL+JoIowmv2I+u09iDmS60BMwSoeK2K3CCLIXxLaiiPYUMsrNk65j4PWEBwBEfyHb6j3lrM/YvwFLq7Y8KJVjrXjFENC7ruja6Ko/cfTMX90yLkUEckpsZ30E6RJHWEHt7jXtNwndDZVknYDZgAACL5pnk17FJfb8AGGxJL1Y0CnAzgkTM2gw+WkFRRDDNzujuW1LQofwZ119HdeANhPrBZ14x32VaPGL1l0RvtR9LCeAN+EogcV5xhVpmCExitaXQB6OkZ6BnXaxLj5TNvFRNeZq0ZfJ3T08clLA1vXHkZhNKgiFDI8xUbahy4r6QpzgoF+0+dz+MA1NzQCQCsRGieS63OD1BKrzRsNxzls5Z9rKzBT6CpWpiaiOg4mmW0yeino/L9zz9Gf5kAJr813bpNr+rH/E8MPd0pZf+6hv37FaVCM7RN1V7CkkCDnRAxwxEK8pDgZhRjZOw7gKutPOiOoTO9ptjh2Jcrds714HitX2HI3RsRY+yyAOcb8XI27m4daSEGCJCuu/TJwXTE4ul54MWsi8MrcDlZN9DOjckiJIqVI8IbvhM+OUAP4FUIfZJJrIVa8WFwxcsMmjlLTxp/I7+JfdvZjJSk3j1yYvbWFviyoSkpQgw2hIDhZxCg083Z6qS467g5H9Uz3fQc+Ss0K0Mud6RcZTU9RqCcp+h92tUc8+gDxQ2NwJsG5vcmSRwf5KHKvsWjt6yK4OHxCpkLYi31eJZtv2EjQGXX1gYyhc/2wQ+cHPvbgBzIfhXetbZKpSxoowAQO/J1i5oRs90h24kjTd4qJd3qspxk1lhZcEC8IkfZXjNgjEQI=");
|
||||
//
|
||||
// OneDriveFolder folder = new OneDriveFolder(api, "PDF");
|
||||
//
|
||||
// for (OneDriveItem.Metadata metadata : OneDriveFolder.getRoot(api)) {
|
||||
// System.out.println(metadata.getName());
|
||||
// if (metadata.isFile()) {
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // for (OneDriveItem.Metadata search : OneDriveFolder.getRoot(api).search("index.html")) {
|
||||
// // System.out.println(search.getName());
|
||||
// // }
|
||||
//
|
||||
// // OneDriveFile file = new OneDriveFile(api, "key.txt");
|
||||
// // System.out.println(file);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,42 @@
|
||||
package im.zhaojun.onedrive.controller;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@Controller
|
||||
public class OneDriveController {
|
||||
|
||||
@Resource
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@GetMapping("/onedirve/callback")
|
||||
@ResponseBody
|
||||
public String onedriveCallback(String code, HttpServletRequest request) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
|
||||
headers.setContentType(type);
|
||||
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
|
||||
|
||||
String json = "client_id=04a73532-6c16-4fe4-92e5-f2cd125ed553&redirect_uri=http://localhost:8080/onedirve/callback&client_secret=2gY/t?*Eff6i36TgKTtiG*08/k]@.I4[&code=" + code + "&grant_type=authorization_code";
|
||||
|
||||
HttpRequest post = HttpUtil.createPost("https://login.microsoftonline.com/common/oauth2/v2.0/token");
|
||||
post.body(json, "application/x-www-form-urlencoded");
|
||||
HttpResponse response = post.execute();
|
||||
|
||||
System.out.println(response.body());
|
||||
return response.body();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
package im.zhaojun.qiniu.service;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.qiniu.common.QiniuException;
|
||||
import com.qiniu.common.Zone;
|
||||
import com.qiniu.storage.BucketManager;
|
||||
import com.qiniu.storage.Configuration;
|
||||
import com.qiniu.storage.model.FileInfo;
|
||||
import com.qiniu.storage.model.FileListing;
|
||||
import com.qiniu.util.Auth;
|
||||
import im.zhaojun.common.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.FileItem;
|
||||
import im.zhaojun.common.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.dto.FileItemDTO;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -25,7 +26,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class QiniuService implements FileService {
|
||||
public class QiniuServiceImpl implements FileService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(QiniuServiceImpl.class);
|
||||
|
||||
@Value("${zfile.cache.timeout}")
|
||||
private Long timeout;
|
||||
@@ -51,25 +54,33 @@ public class QiniuService implements FileService {
|
||||
|
||||
private boolean isPrivate;
|
||||
|
||||
public void initMethod() throws QiniuException {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.QINIU);
|
||||
String accessKey = stringStorageConfigMap.get(ACCESS_KEY).getValue();
|
||||
String secretKey = stringStorageConfigMap.get(SECRET_KEY).getValue();
|
||||
private boolean isInitialized;
|
||||
|
||||
Configuration cfg = new Configuration(Zone.autoZone());
|
||||
auth = Auth.create(accessKey, secretKey);
|
||||
bucketManager = new BucketManager(auth, cfg);
|
||||
bucketName = stringStorageConfigMap.get(BUCKET_NAME_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
@Override
|
||||
public void init() {
|
||||
try {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.QINIU);
|
||||
String accessKey = stringStorageConfigMap.get(ACCESS_KEY).getValue();
|
||||
String secretKey = stringStorageConfigMap.get(SECRET_KEY).getValue();
|
||||
|
||||
isPrivate = bucketManager.getBucketInfo(bucketName).getPrivate() == 1;
|
||||
Configuration cfg = new Configuration(Zone.autoZone());
|
||||
auth = Auth.create(accessKey, secretKey);
|
||||
bucketManager = new BucketManager(auth, cfg);
|
||||
bucketName = stringStorageConfigMap.get(BUCKET_NAME_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
|
||||
isPrivate = bucketManager.getBucketInfo(bucketName).getPrivate() == 1;
|
||||
isInitialized = true;
|
||||
} catch (Exception e) {
|
||||
log.debug(StorageTypeEnum.QINIU.getDescription() + "初始化异常, 已跳过");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItem> fileList(String path) throws Exception {
|
||||
public List<FileItemDTO> fileList(String path) throws Exception {
|
||||
path = StringUtils.removeFirstSeparator(path);
|
||||
List<FileItem> fileItemList = new ArrayList<>();
|
||||
List<FileItemDTO> fileItemList = new ArrayList<>();
|
||||
|
||||
// 每次迭代的长度限制, 最大1000, 推荐值 1000
|
||||
int limit = 1000;
|
||||
@@ -81,23 +92,27 @@ public class QiniuService implements FileService {
|
||||
String fileKey = item.key;
|
||||
String fileName = fileKey.substring(path.length());
|
||||
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setName(fileName);
|
||||
fileItem.setSize(item.fsize);
|
||||
fileItem.setTime(new Date(item.putTime / 1000));
|
||||
fileItem.setType(FileTypeEnum.FILE);
|
||||
fileItem.setPath(path);
|
||||
fileItemList.add(fileItem);
|
||||
FileItemDTO fileItemDTO = new FileItemDTO();
|
||||
fileItemDTO.setName(fileName);
|
||||
fileItemDTO.setSize(item.fsize);
|
||||
fileItemDTO.setTime(new Date(item.putTime / 1000));
|
||||
fileItemDTO.setType(FileTypeEnum.FILE);
|
||||
fileItemDTO.setPath(path);
|
||||
fileItemDTO.setUrl(getDownloadUrl(StringUtils.concatUrl(path, fileItemDTO.getName())));
|
||||
fileItemList.add(fileItemDTO);
|
||||
}
|
||||
|
||||
String[] commonPrefixes = fileListing.commonPrefixes;
|
||||
|
||||
for (String commonPrefix : commonPrefixes) {
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setName(commonPrefix.substring(0, commonPrefix.length() - 1));
|
||||
fileItem.setType(FileTypeEnum.FOLDER);
|
||||
fileItem.setPath(path);
|
||||
fileItemList.add(fileItem);
|
||||
if ("/".equals(commonPrefix)) {
|
||||
continue;
|
||||
}
|
||||
FileItemDTO fileItemDTO = new FileItemDTO();
|
||||
fileItemDTO.setName(commonPrefix.substring(0, commonPrefix.length() - 1));
|
||||
fileItemDTO.setType(FileTypeEnum.FOLDER);
|
||||
fileItemDTO.setPath(path);
|
||||
fileItemList.add(fileItemDTO);
|
||||
}
|
||||
|
||||
return fileItemList;
|
||||
@@ -116,4 +131,11 @@ public class QiniuService implements FileService {
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.QINIU;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getIsInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package im.zhaojun.tencent;
|
||||
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.qcloud.cos.COSClient;
|
||||
import com.qcloud.cos.ClientConfig;
|
||||
import com.qcloud.cos.auth.BasicCOSCredentials;
|
||||
import com.qcloud.cos.auth.COSCredentials;
|
||||
import com.qcloud.cos.model.COSObjectSummary;
|
||||
import com.qcloud.cos.model.ListObjectsRequest;
|
||||
import com.qcloud.cos.model.ObjectListing;
|
||||
import com.qcloud.cos.region.Region;
|
||||
import im.zhaojun.common.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.FileItem;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class TencentService implements FileService {
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
private static final String BUCKET_NAME_KEY = "bucket-name";
|
||||
|
||||
private static final String SECRET_ID_KEY = "secretId";
|
||||
|
||||
private static final String SECRET_KEY = "secretKey";
|
||||
|
||||
private static final String DOMAIN_KEY = "domain";
|
||||
|
||||
private static final String ENDPOINT_KEY = "endPoint";
|
||||
|
||||
@Value("${zfile.cache.timeout}")
|
||||
private Long timeout;
|
||||
|
||||
private String bucketName;
|
||||
|
||||
private String domain;
|
||||
|
||||
private COSClient cosClient;
|
||||
|
||||
@Override
|
||||
public void initMethod() {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.TENCENT);
|
||||
String secretId = stringStorageConfigMap.get(SECRET_ID_KEY).getValue();
|
||||
String secretKey = stringStorageConfigMap.get(SECRET_KEY).getValue();
|
||||
String endPoint = stringStorageConfigMap.get(ENDPOINT_KEY).getValue();
|
||||
bucketName = stringStorageConfigMap.get(BUCKET_NAME_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
|
||||
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
|
||||
Region region = new Region("ap-shanghai");
|
||||
ClientConfig clientConfig = new ClientConfig(region);
|
||||
// clientConfig.setSignExpired();
|
||||
cosClient = new COSClient(cred, clientConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItem> fileList(String path) {
|
||||
path = StringUtils.removeFirstSeparator(path);
|
||||
|
||||
List<FileItem> fileItemList = new ArrayList<>();
|
||||
ObjectListing objectListing = cosClient.listObjects(new ListObjectsRequest().withBucketName(bucketName).withDelimiter("/").withPrefix(path));
|
||||
for (COSObjectSummary s : objectListing.getObjectSummaries()) {
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setName(s.getKey().substring(path.length()));
|
||||
fileItem.setSize(s.getSize());
|
||||
fileItem.setTime(s.getLastModified());
|
||||
fileItem.setType(FileTypeEnum.FILE);
|
||||
fileItem.setPath(path);
|
||||
fileItemList.add(fileItem);
|
||||
}
|
||||
|
||||
for (String commonPrefix : objectListing.getCommonPrefixes()) {
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setName(commonPrefix.substring(path.length(), commonPrefix.length() - 1));
|
||||
fileItem.setType(FileTypeEnum.FOLDER);
|
||||
fileItem.setPath(path);
|
||||
fileItemList.add(fileItem);
|
||||
}
|
||||
|
||||
return fileItemList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl(String path) {
|
||||
Date expirationDate = new Date(new Date().getTime() + timeout * 1000);
|
||||
URL url = cosClient.generatePresignedUrl(bucketName, path, expirationDate);
|
||||
return URLUtil.complateUrl(domain, url.getFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.TENCENT;
|
||||
}
|
||||
}
|
||||
128
src/main/java/im/zhaojun/tencent/TencentServiceImpl.java
Normal file
128
src/main/java/im/zhaojun/tencent/TencentServiceImpl.java
Normal file
@@ -0,0 +1,128 @@
|
||||
package im.zhaojun.tencent;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.qcloud.cos.COSClient;
|
||||
import com.qcloud.cos.ClientConfig;
|
||||
import com.qcloud.cos.auth.BasicCOSCredentials;
|
||||
import com.qcloud.cos.auth.COSCredentials;
|
||||
import com.qcloud.cos.model.COSObjectSummary;
|
||||
import com.qcloud.cos.model.ListObjectsRequest;
|
||||
import com.qcloud.cos.model.ObjectListing;
|
||||
import com.qcloud.cos.region.Region;
|
||||
import im.zhaojun.common.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.dto.FileItemDTO;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class TencentServiceImpl implements FileService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TencentServiceImpl.class);
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
private static final String BUCKET_NAME_KEY = "bucket-name";
|
||||
|
||||
private static final String SECRET_ID_KEY = "secretId";
|
||||
|
||||
private static final String SECRET_KEY = "secretKey";
|
||||
|
||||
private static final String DOMAIN_KEY = "domain";
|
||||
|
||||
private static final String ENDPOINT_KEY = "endPoint";
|
||||
|
||||
@Value("${zfile.cache.timeout}")
|
||||
private Long timeout;
|
||||
|
||||
private String bucketName;
|
||||
|
||||
private String domain;
|
||||
|
||||
private COSClient cosClient;
|
||||
|
||||
private boolean isInitialized;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
try {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.TENCENT);
|
||||
String secretId = stringStorageConfigMap.get(SECRET_ID_KEY).getValue();
|
||||
String secretKey = stringStorageConfigMap.get(SECRET_KEY).getValue();
|
||||
String regionName = stringStorageConfigMap.get(ENDPOINT_KEY).getValue();
|
||||
bucketName = stringStorageConfigMap.get(BUCKET_NAME_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
|
||||
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
|
||||
Region region = new Region(regionName);
|
||||
ClientConfig clientConfig = new ClientConfig(region);
|
||||
cosClient = new COSClient(cred, clientConfig);
|
||||
isInitialized = true;
|
||||
} catch (Exception e) {
|
||||
log.debug(StorageTypeEnum.TENCENT.getDescription() + "初始化异常, 已跳过");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItemDTO> fileList(String path) {
|
||||
path = StringUtils.removeFirstSeparator(path);
|
||||
|
||||
List<FileItemDTO> fileItemList = new ArrayList<>();
|
||||
ObjectListing objectListing = cosClient.listObjects(new ListObjectsRequest().withBucketName(bucketName).withDelimiter("/").withPrefix(path));
|
||||
for (COSObjectSummary s : objectListing.getObjectSummaries()) {
|
||||
FileItemDTO fileItemDTO = new FileItemDTO();
|
||||
fileItemDTO.setName(s.getKey().substring(path.length()));
|
||||
fileItemDTO.setSize(s.getSize());
|
||||
fileItemDTO.setTime(s.getLastModified());
|
||||
fileItemDTO.setType(FileTypeEnum.FILE);
|
||||
fileItemDTO.setPath(path);
|
||||
fileItemDTO.setUrl(getDownloadUrl(StringUtils.concatUrl(path, fileItemDTO.getName())));
|
||||
fileItemList.add(fileItemDTO);
|
||||
}
|
||||
|
||||
for (String commonPrefix : objectListing.getCommonPrefixes()) {
|
||||
FileItemDTO fileItemDTO = new FileItemDTO();
|
||||
fileItemDTO.setName(commonPrefix.substring(path.length(), commonPrefix.length() - 1));
|
||||
fileItemDTO.setType(FileTypeEnum.FOLDER);
|
||||
fileItemDTO.setPath(path);
|
||||
fileItemList.add(fileItemDTO);
|
||||
}
|
||||
|
||||
return fileItemList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl(String path) {
|
||||
Date expirationDate = new Date(System.currentTimeMillis() + timeout * 1000);
|
||||
URL url = cosClient.generatePresignedUrl(bucketName, path, expirationDate);
|
||||
return URLUtil.complateUrl(domain, url.getFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.TENCENT;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean getIsInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package im.zhaojun.upyun.service;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.UpYun;
|
||||
import im.zhaojun.common.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.FileItem;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class UpYunService implements FileService {
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
private static final String BUCKET_NAME_KEY = "bucket-name";
|
||||
|
||||
private static final String USERNAME_KEY = "username";
|
||||
|
||||
private static final String PASSWORD_KEY = "password";
|
||||
|
||||
private static final String DOMAIN_KEY = "domain";
|
||||
|
||||
private String domain;
|
||||
|
||||
private UpYun upYun;
|
||||
|
||||
public void initMethod() {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.UPYUN);
|
||||
String bucketName = stringStorageConfigMap.get(BUCKET_NAME_KEY).getValue();
|
||||
String username = stringStorageConfigMap.get(USERNAME_KEY).getValue();
|
||||
String password = stringStorageConfigMap.get(PASSWORD_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
upYun = new UpYun(bucketName, username, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItem> fileList(String path) throws Exception {
|
||||
ArrayList<FileItem> fileItems = new ArrayList<>();
|
||||
String nextMark = null;
|
||||
|
||||
do {
|
||||
HashMap<String, String> hashMap = new HashMap<>();
|
||||
hashMap.put("x-list-iter", nextMark);
|
||||
hashMap.put("x-list-limit", "100");
|
||||
UpYun.FolderItemIter folderItemIter = upYun.readDirIter(URLUtil.encode(path), hashMap);
|
||||
nextMark = folderItemIter.iter;
|
||||
ArrayList<UpYun.FolderItem> folderItems = folderItemIter.files;
|
||||
if (folderItems != null) {
|
||||
for (UpYun.FolderItem folderItem : folderItems) {
|
||||
FileItem fileItem = new FileItem();
|
||||
fileItem.setName(folderItem.name);
|
||||
fileItem.setSize(folderItem.size);
|
||||
fileItem.setTime(folderItem.date);
|
||||
fileItem.setPath(path);
|
||||
|
||||
if ("folder".equals(folderItem.type)) {
|
||||
fileItem.setType(FileTypeEnum.FOLDER);
|
||||
} else {
|
||||
fileItem.setType(FileTypeEnum.FILE);
|
||||
}
|
||||
fileItems.add(fileItem);
|
||||
}
|
||||
}
|
||||
} while (!"g2gCZAAEbmV4dGQAA2VvZg".equals(nextMark));
|
||||
return fileItems;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl(String path) {
|
||||
return URLUtil.complateUrl(domain, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.UPYUN;
|
||||
}
|
||||
}
|
||||
111
src/main/java/im/zhaojun/upyun/service/UpYunServiceImpl.java
Normal file
111
src/main/java/im/zhaojun/upyun/service/UpYunServiceImpl.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package im.zhaojun.upyun.service;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.UpYun;
|
||||
import im.zhaojun.common.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.common.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.common.model.dto.FileItemDTO;
|
||||
import im.zhaojun.common.model.StorageConfig;
|
||||
import im.zhaojun.common.service.FileService;
|
||||
import im.zhaojun.common.service.StorageConfigService;
|
||||
import im.zhaojun.common.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class UpYunServiceImpl implements FileService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(UpYunServiceImpl.class);
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
private static final String BUCKET_NAME_KEY = "bucket-name";
|
||||
|
||||
private static final String USERNAME_KEY = "username";
|
||||
|
||||
private static final String PASSWORD_KEY = "password";
|
||||
|
||||
private static final String DOMAIN_KEY = "domain";
|
||||
|
||||
private String domain;
|
||||
|
||||
private UpYun upYun;
|
||||
|
||||
private boolean isInitialized;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
try {
|
||||
Map<String, StorageConfig> stringStorageConfigMap =
|
||||
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.UPYUN);
|
||||
String bucketName = stringStorageConfigMap.get(BUCKET_NAME_KEY).getValue();
|
||||
String username = stringStorageConfigMap.get(USERNAME_KEY).getValue();
|
||||
String password = stringStorageConfigMap.get(PASSWORD_KEY).getValue();
|
||||
domain = stringStorageConfigMap.get(DOMAIN_KEY).getValue();
|
||||
upYun = new UpYun(bucketName, username, password);
|
||||
isInitialized = true;
|
||||
} catch (Exception e) {
|
||||
log.debug(StorageTypeEnum.UPYUN.getDescription() + "初始化异常, 已跳过");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItemDTO> fileList(String path) throws Exception {
|
||||
ArrayList<FileItemDTO> fileItemList = new ArrayList<>();
|
||||
String nextMark = null;
|
||||
|
||||
do {
|
||||
HashMap<String, String> hashMap = new HashMap<>(24);
|
||||
hashMap.put("x-list-iter", nextMark);
|
||||
hashMap.put("x-list-limit", "100");
|
||||
UpYun.FolderItemIter folderItemIter = upYun.readDirIter(URLUtil.encode(path), hashMap);
|
||||
nextMark = folderItemIter.iter;
|
||||
ArrayList<UpYun.FolderItem> folderItems = folderItemIter.files;
|
||||
if (folderItems != null) {
|
||||
for (UpYun.FolderItem folderItem : folderItems) {
|
||||
FileItemDTO fileItemDTO = new FileItemDTO();
|
||||
fileItemDTO.setName(folderItem.name);
|
||||
fileItemDTO.setSize(folderItem.size);
|
||||
fileItemDTO.setTime(folderItem.date);
|
||||
fileItemDTO.setPath(path);
|
||||
|
||||
if ("folder".equals(folderItem.type)) {
|
||||
fileItemDTO.setType(FileTypeEnum.FOLDER);
|
||||
} else {
|
||||
fileItemDTO.setType(FileTypeEnum.FILE);
|
||||
fileItemDTO.setUrl(getDownloadUrl(StringUtils.concatUrl(path, fileItemDTO.getName())));
|
||||
}
|
||||
fileItemList.add(fileItemDTO);
|
||||
}
|
||||
}
|
||||
} while (!"g2gCZAAEbmV4dGQAA2VvZg".equals(nextMark));
|
||||
return fileItemList;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl(String path) {
|
||||
return URLUtil.complateUrl(domain, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.UPYUN;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean getIsInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# \u76EE\u5F55\u7F13\u5B58\u8FC7\u671F\u65F6\u95F4 \u548C \u4E0B\u8F7D\u5730\u5740\u8FC7\u671F\u65F6\u95F4. \u5355\u4F4D\u4E3A\u79D2.
|
||||
zfile.cache.timeout = 300
|
||||
|
||||
spring.cache.type=redis
|
||||
|
||||
spring.jackson.date-format=yyyy-MM-dd HH:mm
|
||||
spring.jackson.time-zone=GMT+8
|
||||
|
||||
spring.datasource.driver-class-name=org.h2.Driver
|
||||
spring.datasource.url=jdbc:h2:tcp://localhost/~/zfile/h2/zfiledb
|
||||
#spring.datasource.url=jdbc:h2:~/zfile/h2/zfiledb
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
|
||||
logging.level.im.zhaojun.common = debug
|
||||
54
src/main/resources/application.yaml
Normal file
54
src/main/resources/application.yaml
Normal file
@@ -0,0 +1,54 @@
|
||||
logging:
|
||||
level:
|
||||
im:
|
||||
zhaojun:
|
||||
common: debug
|
||||
path: ${user.home}/.zfile/logs
|
||||
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
context-path: ''
|
||||
|
||||
|
||||
spring:
|
||||
cache:
|
||||
type: redis
|
||||
datasource:
|
||||
# 初始化数据导入
|
||||
data: classpath*:db/data.sql
|
||||
initialization-mode: always
|
||||
continue-on-error: true
|
||||
|
||||
# h2 内存数据库 配置
|
||||
# driver-class-name: org.h2.Driver
|
||||
# url: jdbc:h2:tcp://localhost/~/.zfile/db/zfile
|
||||
# username: zfile
|
||||
# password: 123456
|
||||
|
||||
# MySQL 配置
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
url: jdbc:mysql://127.0.0.1:3306/zfile?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
|
||||
username: root
|
||||
password: 123456
|
||||
jackson:
|
||||
date-format: yyyy-MM-dd HH:mm
|
||||
time-zone: GMT+8
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: false
|
||||
show-sql: true
|
||||
resources:
|
||||
chain:
|
||||
gzipped: true
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
password: 12345
|
||||
zfile:
|
||||
cache:
|
||||
timeout: 300
|
||||
|
||||
|
||||
37
src/main/resources/db/data.sql
Normal file
37
src/main/resources/db/data.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
INSERT INTO SYSTEM_CONFIG (`ID`, `KEY`, `REMARK`, `VALUE`) VALUES (1, 'siteName', '站点名称', null);
|
||||
INSERT INTO SYSTEM_CONFIG (`ID`, `KEY`, `REMARK`, `VALUE`) VALUES (2, 'infoEnable', '是否开启信息框', null);
|
||||
INSERT INTO SYSTEM_CONFIG (`ID`, `KEY`, `REMARK`, `VALUE`) VALUES (3, 'searchEnable', '是否开启搜索', null);
|
||||
INSERT INTO SYSTEM_CONFIG (`ID`, `KEY`, `REMARK`, `VALUE`) VALUES (4, 'searchIgnoreCase', '是否搜索时忽略大小写', null);
|
||||
INSERT INTO SYSTEM_CONFIG (`ID`, `KEY`, `REMARK`, `VALUE`) VALUES (5, 'storageStrategy', '当前启用存储引擎', null);
|
||||
INSERT INTO SYSTEM_CONFIG (`ID`, `KEY`, `REMARK`, `VALUE`) VALUES (6, 'username', '管理员账号', null);
|
||||
INSERT INTO SYSTEM_CONFIG (`ID`, `KEY`, `REMARK`, `VALUE`) VALUES (7, 'password', '管理员密码', null);
|
||||
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (1, 'bucket-name', '云存储服务名称', 'upyun', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (2, 'username', '操作员名称', 'upyun', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (3, 'password', '操作员密码', 'upyun', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (4, 'domain', '加速域名', 'upyun', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (5, 'accessKey', 'AccessKey', 'qiniu', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (6, 'secretKey', 'SecretKey', 'qiniu', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (7, 'bucket-name', '存储空间名称', 'qiniu', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (8, 'domain', '加速域名', 'qiniu', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (9, 'accessKey', 'AccessKey', 'huawei', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (10, 'secretKey', 'SecretKey', 'huawei', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (11, 'bucket-name', '云存储服务名称', 'huawei', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (12, 'domain', '加速域名', 'huawei', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (13, 'endPoint', '区域', 'huawei', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (14, 'accessKey', 'AccessKey', 'aliyun', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (15, 'secretKey', 'SecretKey', 'aliyun', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (16, 'bucket-name', 'Bucket 名称', 'aliyun', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (17, 'domain', 'Bucket 域名 / CDN 加速域名', 'aliyun', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (18, 'endPoint', '区域', 'aliyun', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (19, 'filePath', '文件路径', 'local', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (20, 'host', '域名或IP', 'ftp', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (21, 'port', '端口', 'ftp', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (22, 'username', '用户名', 'ftp', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (23, 'password', '密码', 'ftp', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (24, 'domain', '域名', 'ftp', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (25, 'secretId', 'SecretId', 'tencent', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (26, 'secretKey', 'SecretKey', 'tencent', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (27, 'bucket-name', '云存储服务名称', 'tencent', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (28, 'domain', '加速域名', 'tencent', null);
|
||||
INSERT INTO STORAGE_CONFIG (`ID`, `KEY`, `TITLE`, `TYPE`, `VALUE`) VALUES (29, 'endPoint', '区域', 'tencent', null);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,142 +0,0 @@
|
||||
<p align="center">
|
||||
<img src="https://ws4.sinaimg.cn/large/006tKfTcgy1fhu01y9uy7j305k04s3yc.jpg" alt="ADPlayer" width="100">
|
||||
</p>
|
||||
<h1 align="center">DPlayer</h1>
|
||||
|
||||
> 🍭 Wow, such a lovely HTML5 danmaku video player
|
||||
|
||||
[](https://www.npmjs.com/package/dplayer)
|
||||
[](https://github.com/MoePlayer/DPlayer/blob/master/LICENSE)
|
||||
[](https://www.npmjs.com/package/dplayer)
|
||||
[](https://github.com/MoePlayer/DPlayer/tree/master/dist)
|
||||
[](https://travis-ci.org/MoePlayer/DPlayer)
|
||||
[](https://david-dm.org/MoePlayer/DPlayer#info=devDependencies)
|
||||
[](https://github.com/MoePlayer/DPlayer#donate)
|
||||
|
||||
## Introduction
|
||||
|
||||

|
||||
|
||||
DPlayer is a lovely HTML5 danmaku video player to help people build video and danmaku easily.
|
||||
|
||||
**DPlayer supports:**
|
||||
|
||||
- Streaming formats
|
||||
- [HLS](https://github.com/video-dev/hls.js)
|
||||
- [FLV](https://github.com/Bilibili/flv.js)
|
||||
- [MPEG DASH](https://github.com/Dash-Industry-Forum/dash.js)
|
||||
- [WebTorrent](https://github.com/webtorrent/webtorrent)
|
||||
- Any other custom streaming formats
|
||||
- Media formats
|
||||
- MP4 H.264
|
||||
- WebM
|
||||
- Ogg Theora Vorbis
|
||||
- Features
|
||||
- Danmaku
|
||||
- Screenshot
|
||||
- Hotkeys
|
||||
- Quality switching
|
||||
- Thumbnails
|
||||
- Subtitle
|
||||
|
||||
Using DPlayer on your project? [Let me know!](https://github.com/DIYgod/DPlayer/issues/31)
|
||||
|
||||
**[Docs](http://dplayer.js.org)**
|
||||
|
||||
**[中文文档](http://dplayer.js.org/#/zh-Hans/)**
|
||||
|
||||
## Join the Discussion
|
||||
|
||||
- [Telegram Group](https://t.me/adplayer)
|
||||
|
||||
## Related Projects
|
||||
|
||||
Feel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/DPlayer/issues/31)
|
||||
|
||||
### Tooling
|
||||
|
||||
- [DPlayer-thumbnails](https://github.com/MoePlayer/DPlayer-thumbnails): generate video thumbnails
|
||||
|
||||
### Danmaku api
|
||||
|
||||
- [DPlayer-node](https://github.com/MoePlayer/DPlayer-node): Node.js
|
||||
- [laravel-danmaku](https://github.com/MoePlayer/laravel-danmaku): PHP
|
||||
- [dplayer-live-backend](https://github.com/Izumi-kun/dplayer-live-backend): Node.js, WebSocket live backend
|
||||
- [RailsGun](https://github.com/MoePlayer/RailsGun): Ruby
|
||||
|
||||
### Plugins
|
||||
|
||||
- [DPlayer-for-typecho](https://github.com/volio/DPlayer-for-typecho): Typecho
|
||||
- [Hexo-tag-dplayer](https://github.com/NextMoe/hexo-tag-dplayer): Hexo
|
||||
- [DPlayer_for_Z-BlogPHP](https://github.com/fghrsh/DPlayer_for_Z-BlogPHP): Z-BlogPHP
|
||||
- [DPlayer for Discuz!](https://coding.net/u/Click_04/p/video/git): Discuz!
|
||||
- [DPlayer for WordPress](https://github.com/BlueCocoa/DPlayer-WordPress): WordPress
|
||||
- [DPlayerHandle](https://github.com/kn007/DPlayerHandle): WordPress
|
||||
- [Vue-DPlayer](https://github.com/sinchang/vue-dplayer): Vue
|
||||
- [react-dplayer](https://github.com/hnsylitao/react-dplayer): React
|
||||
|
||||
### Other
|
||||
|
||||
- [DPlayer-Lite](https://github.com/kn007/DPlayer-Lite): lite version
|
||||
- [hlsjs-p2p-engine](https://github.com/cdnbye/hlsjs-p2p-engine)
|
||||
- Feel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/DPlayer/issues/31)
|
||||
|
||||
## Who use DPlayer?
|
||||
|
||||
- [小红书](https://www.xiaohongshu.com/): 中国最大的生活社区分享平台,同时也是发现全球好物的电商平台
|
||||
- [极客时间](https://time.geekbang.org/): 极客邦科技出品的一款 IT 内容知识服务 App
|
||||
- [嘀哩嘀哩](http://www.dilidili.wang/): 兴趣使然的无名小站(D站)
|
||||
- [银色子弹](https://www.sbsub.com/): 银色子弹,简称银弹,由多数柯南热爱者聚集在一起的组织
|
||||
- [浙江大学CC98论坛](https://zh.wikipedia.org/wiki/CC98%E8%AE%BA%E5%9D%9B): 浙江大学校网内规模最大的论坛,中国各大学中较活跃的BBS之一
|
||||
- [纸飞机南航青年网络社区](http://my.nuaa.edu.cn/video-video.html): 南京航空航天大学门户网站
|
||||
- [otomads](https://otomads.com/): 专注于音MAD的视频弹幕网站
|
||||
- [Cloudreve](https://github.com/HFO4/Cloudreve): 基于ThinkPHP构建的网盘系统
|
||||
- Feel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/DPlayer/issues/31)
|
||||
|
||||
## Current Premium Sponsors
|
||||
|
||||
### Special Sponsors
|
||||
|
||||
<a href="https://pear.hk/" target="_blank">
|
||||
<img width="222px" src="https://i.imgur.com/5qQYmfc.png">
|
||||
</a>
|
||||
<a href="https://console.upyun.com/register/?invite=BkLZ2Xqob" target="_blank">
|
||||
<img width="222px" src="https://imgur.com/apG1uKf.png">
|
||||
</a>
|
||||
|
||||
### OpenCollective backers
|
||||
|
||||

|
||||
|
||||
## Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
|
||||
<a href="https://github.com/MoePlayer/DPlayer/graphs/contributors"><img src="https://opencollective.com/DPlayer/contributors.svg?width=890" /></a>
|
||||
|
||||
## Donate
|
||||
|
||||
DPlayer is an MIT licensed open source project and completely free to use. However, the amount of effort needed to maintain and develop new features for the project is not sustainable without proper financial backing.
|
||||
|
||||
## One-time Donations
|
||||
|
||||
We accept donations through these channels:
|
||||
|
||||
- [Paypal](https://www.paypal.me/DIYgod)
|
||||
- [WeChat Pay](https://i.imgur.com/aq6PtWa.png)
|
||||
- [Alipay](https://i.imgur.com/wv1Pj2k.png)
|
||||
- Bitcoin: 13CwQLHzPYm2tewNMSJBeArbbRM5NSmCD1
|
||||
|
||||
## Recurring Pledges
|
||||
|
||||
Recurring pledges come with exclusive perks, e.g. having your name or your company logo listed in the DPlayer GitHub repository and this website.
|
||||
|
||||
- Become a backer or sponsor via [OpenCollective](https://opencollective.com/dplayer)
|
||||
- E-mail us: i#html.love
|
||||
|
||||
## Author
|
||||
|
||||
**DPlayer** © [DIYgod](https://github.com/DIYgod), Released under the [MIT](./LICENSE) License.<br>
|
||||
Authored and maintained by DIYgod with help from contributors ([list](https://github.com/DIYgod/DPlayer/contributors)).
|
||||
|
||||
> [Blog](https://diygod.me) · GitHub [@DIYgod](https://github.com/DIYgod) · Twitter [@DIYgod](https://twitter.com/DIYgod) · Telegram Channel [@awesomeDIYgod](https://t.me/awesomeDIYgod)
|
||||
@@ -1,22 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title></title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<link rel="stylesheet" href="DPlayer.min.css">
|
||||
<div id="dplayer"></div>
|
||||
<script src="DPlayer.min.js"></script>
|
||||
<script>
|
||||
const dp = new DPlayer({
|
||||
container: document.getElementById('dplayer'),
|
||||
video: {
|
||||
url: decodeURIComponent(window.location.hash.substring(1))
|
||||
},
|
||||
autoplay: true
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,136 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* The main class required to set up an Ace instance in the browser.
|
||||
*
|
||||
* @class Ace
|
||||
**/
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
require("./lib/fixoldbrowsers");
|
||||
|
||||
var dom = require("./lib/dom");
|
||||
var event = require("./lib/event");
|
||||
|
||||
var Range = require("./range").Range;
|
||||
var Editor = require("./editor").Editor;
|
||||
var EditSession = require("./edit_session").EditSession;
|
||||
var UndoManager = require("./undomanager").UndoManager;
|
||||
var Renderer = require("./virtual_renderer").VirtualRenderer;
|
||||
|
||||
// The following require()s are for inclusion in the built ace file
|
||||
require("./worker/worker_client");
|
||||
require("./keyboard/hash_handler");
|
||||
require("./placeholder");
|
||||
require("./multi_select");
|
||||
require("./mode/folding/fold_mode");
|
||||
require("./theme/textmate");
|
||||
require("./ext/error_marker");
|
||||
|
||||
exports.config = require("./config");
|
||||
|
||||
/**
|
||||
* Provides access to require in packed noconflict mode
|
||||
* @param {String} moduleName
|
||||
* @returns {Object}
|
||||
**/
|
||||
exports.require = require;
|
||||
|
||||
if (typeof define === "function")
|
||||
exports.define = define;
|
||||
|
||||
/**
|
||||
* Embeds the Ace editor into the DOM, at the element provided by `el`.
|
||||
* @param {String | DOMElement} el Either the id of an element, or the element itself
|
||||
* @param {Object } options Options for the editor
|
||||
*
|
||||
**/
|
||||
exports.edit = function(el, options) {
|
||||
if (typeof el == "string") {
|
||||
var _id = el;
|
||||
el = document.getElementById(_id);
|
||||
if (!el)
|
||||
throw new Error("ace.edit can't find div #" + _id);
|
||||
}
|
||||
|
||||
if (el && el.env && el.env.editor instanceof Editor)
|
||||
return el.env.editor;
|
||||
|
||||
var value = "";
|
||||
if (el && /input|textarea/i.test(el.tagName)) {
|
||||
var oldNode = el;
|
||||
value = oldNode.value;
|
||||
el = dom.createElement("pre");
|
||||
oldNode.parentNode.replaceChild(el, oldNode);
|
||||
} else if (el) {
|
||||
value = el.textContent;
|
||||
el.innerHTML = "";
|
||||
}
|
||||
|
||||
var doc = exports.createEditSession(value);
|
||||
|
||||
var editor = new Editor(new Renderer(el), doc, options);
|
||||
|
||||
var env = {
|
||||
document: doc,
|
||||
editor: editor,
|
||||
onResize: editor.resize.bind(editor, null)
|
||||
};
|
||||
if (oldNode) env.textarea = oldNode;
|
||||
event.addListener(window, "resize", env.onResize);
|
||||
editor.on("destroy", function() {
|
||||
event.removeListener(window, "resize", env.onResize);
|
||||
env.editor.container.env = null; // prevent memory leak on old ie
|
||||
});
|
||||
editor.container.env = editor.env = env;
|
||||
return editor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new [[EditSession]], and returns the associated [[Document]].
|
||||
* @param {Document | String} text {:textParam}
|
||||
* @param {TextMode} mode {:modeParam}
|
||||
*
|
||||
**/
|
||||
exports.createEditSession = function(text, mode) {
|
||||
var doc = new EditSession(text, mode);
|
||||
doc.setUndoManager(new UndoManager());
|
||||
return doc;
|
||||
};
|
||||
exports.Range = Range;
|
||||
exports.Editor = Editor;
|
||||
exports.EditSession = EditSession;
|
||||
exports.UndoManager = UndoManager;
|
||||
exports.VirtualRenderer = Renderer;
|
||||
exports.version = exports.config.version;
|
||||
});
|
||||
@@ -1,89 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
if (typeof process !== "undefined") {
|
||||
require("amd-loader");
|
||||
require("./test/mockdom");
|
||||
}
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var ace = require("./ace");
|
||||
var assert = require("./test/assertions");
|
||||
|
||||
module.exports = {
|
||||
"test: ace edit" : function() {
|
||||
var editor = ace.edit(null, {
|
||||
value: "Helo world"
|
||||
});
|
||||
assert.equal(editor.getValue(), "Helo world");
|
||||
|
||||
var session = ace.createEditSession("Juhu kinners!");
|
||||
editor.setSession(session);
|
||||
assert.equal(editor.getValue(), "Juhu kinners!");
|
||||
|
||||
assert.equal(editor, ace.edit(editor.container));
|
||||
editor.destroy();
|
||||
},
|
||||
"test: edit textarea" : function() {
|
||||
var el = document.createElement("textarea");
|
||||
document.body.appendChild(el);
|
||||
var editor = ace.edit(el);
|
||||
assert.notEqual(editor.container, el);
|
||||
|
||||
editor.container.id = "editor1";
|
||||
assert.equal(editor, ace.edit("editor1"));
|
||||
editor.destroy();
|
||||
document.body.removeChild(editor.container);
|
||||
},
|
||||
"test: edit element by id" : function() {
|
||||
var el = document.createElement("div");
|
||||
document.body.appendChild(el);
|
||||
var editor = null;
|
||||
try {
|
||||
editor = ace.edit("x");
|
||||
} catch(e) {
|
||||
}
|
||||
assert.equal(editor, null);
|
||||
|
||||
el.id = "editor2";
|
||||
el.textContent = "h";
|
||||
editor = ace.edit("editor2");
|
||||
assert.equal(el, editor.container);
|
||||
assert.equal("h", editor.getValue());
|
||||
document.body.removeChild(el);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if (typeof module !== "undefined" && module === require.main) {
|
||||
require("asyncjs").test.testcase(module.exports).exec();
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var oop = require("./lib/oop");
|
||||
var EventEmitter = require("./lib/event_emitter").EventEmitter;
|
||||
|
||||
/**
|
||||
*
|
||||
* Defines a floating pointer in the document. Whenever text is inserted or deleted before the cursor, the position of the anchor is updated.
|
||||
*
|
||||
* @class Anchor
|
||||
**/
|
||||
|
||||
/**
|
||||
* Creates a new `Anchor` and associates it with a document.
|
||||
*
|
||||
* @param {Document} doc The document to associate with the anchor
|
||||
* @param {Number} row The starting row position
|
||||
* @param {Number} column The starting column position
|
||||
*
|
||||
* @constructor
|
||||
**/
|
||||
|
||||
var Anchor = exports.Anchor = function(doc, row, column) {
|
||||
this.$onChange = this.onChange.bind(this);
|
||||
this.attach(doc);
|
||||
|
||||
if (typeof column == "undefined")
|
||||
this.setPosition(row.row, row.column);
|
||||
else
|
||||
this.setPosition(row, column);
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
oop.implement(this, EventEmitter);
|
||||
|
||||
/**
|
||||
* Returns an object identifying the `row` and `column` position of the current anchor.
|
||||
* @returns {Object}
|
||||
**/
|
||||
this.getPosition = function() {
|
||||
return this.$clipPositionToDocument(this.row, this.column);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns the current document.
|
||||
* @returns {Document}
|
||||
**/
|
||||
this.getDocument = function() {
|
||||
return this.document;
|
||||
};
|
||||
|
||||
/**
|
||||
* experimental: allows anchor to stick to the next on the left
|
||||
*/
|
||||
this.$insertRight = false;
|
||||
/**
|
||||
* Fires whenever the anchor position changes.
|
||||
*
|
||||
* Both of these objects have a `row` and `column` property corresponding to the position.
|
||||
*
|
||||
* Events that can trigger this function include [[Anchor.setPosition `setPosition()`]].
|
||||
*
|
||||
* @event change
|
||||
* @param {Object} e An object containing information about the anchor position. It has two properties:
|
||||
* - `old`: An object describing the old Anchor position
|
||||
* - `value`: An object describing the new Anchor position
|
||||
*
|
||||
**/
|
||||
this.onChange = function(delta) {
|
||||
if (delta.start.row == delta.end.row && delta.start.row != this.row)
|
||||
return;
|
||||
|
||||
if (delta.start.row > this.row)
|
||||
return;
|
||||
|
||||
var point = $getTransformedPoint(delta, {row: this.row, column: this.column}, this.$insertRight);
|
||||
this.setPosition(point.row, point.column, true);
|
||||
};
|
||||
|
||||
function $pointsInOrder(point1, point2, equalPointsInOrder) {
|
||||
var bColIsAfter = equalPointsInOrder ? point1.column <= point2.column : point1.column < point2.column;
|
||||
return (point1.row < point2.row) || (point1.row == point2.row && bColIsAfter);
|
||||
}
|
||||
|
||||
function $getTransformedPoint(delta, point, moveIfEqual) {
|
||||
// Get delta info.
|
||||
var deltaIsInsert = delta.action == "insert";
|
||||
var deltaRowShift = (deltaIsInsert ? 1 : -1) * (delta.end.row - delta.start.row);
|
||||
var deltaColShift = (deltaIsInsert ? 1 : -1) * (delta.end.column - delta.start.column);
|
||||
var deltaStart = delta.start;
|
||||
var deltaEnd = deltaIsInsert ? deltaStart : delta.end; // Collapse insert range.
|
||||
|
||||
// DELTA AFTER POINT: No change needed.
|
||||
if ($pointsInOrder(point, deltaStart, moveIfEqual)) {
|
||||
return {
|
||||
row: point.row,
|
||||
column: point.column
|
||||
};
|
||||
}
|
||||
|
||||
// DELTA BEFORE POINT: Move point by delta shift.
|
||||
if ($pointsInOrder(deltaEnd, point, !moveIfEqual)) {
|
||||
return {
|
||||
row: point.row + deltaRowShift,
|
||||
column: point.column + (point.row == deltaEnd.row ? deltaColShift : 0)
|
||||
};
|
||||
}
|
||||
|
||||
// DELTA ENVELOPS POINT (delete only): Move point to delta start.
|
||||
// TODO warn if delta.action != "remove" ?
|
||||
|
||||
return {
|
||||
row: deltaStart.row,
|
||||
column: deltaStart.column
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the anchor position to the specified row and column. If `noClip` is `true`, the position is not clipped.
|
||||
* @param {Number} row The row index to move the anchor to
|
||||
* @param {Number} column The column index to move the anchor to
|
||||
* @param {Boolean} noClip Identifies if you want the position to be clipped
|
||||
*
|
||||
**/
|
||||
this.setPosition = function(row, column, noClip) {
|
||||
var pos;
|
||||
if (noClip) {
|
||||
pos = {
|
||||
row: row,
|
||||
column: column
|
||||
};
|
||||
} else {
|
||||
pos = this.$clipPositionToDocument(row, column);
|
||||
}
|
||||
|
||||
if (this.row == pos.row && this.column == pos.column)
|
||||
return;
|
||||
|
||||
var old = {
|
||||
row: this.row,
|
||||
column: this.column
|
||||
};
|
||||
|
||||
this.row = pos.row;
|
||||
this.column = pos.column;
|
||||
this._signal("change", {
|
||||
old: old,
|
||||
value: pos
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* When called, the `"change"` event listener is removed.
|
||||
*
|
||||
**/
|
||||
this.detach = function() {
|
||||
this.document.removeEventListener("change", this.$onChange);
|
||||
};
|
||||
this.attach = function(doc) {
|
||||
this.document = doc || this.document;
|
||||
this.document.on("change", this.$onChange);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clips the anchor position to the specified row and column.
|
||||
* @param {Number} row The row index to clip the anchor to
|
||||
* @param {Number} column The column index to clip the anchor to
|
||||
*
|
||||
**/
|
||||
this.$clipPositionToDocument = function(row, column) {
|
||||
var pos = {};
|
||||
|
||||
if (row >= this.document.getLength()) {
|
||||
pos.row = Math.max(0, this.document.getLength() - 1);
|
||||
pos.column = this.document.getLine(pos.row).length;
|
||||
}
|
||||
else if (row < 0) {
|
||||
pos.row = 0;
|
||||
pos.column = 0;
|
||||
}
|
||||
else {
|
||||
pos.row = row;
|
||||
pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column));
|
||||
}
|
||||
|
||||
if (column < 0)
|
||||
pos.column = 0;
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
}).call(Anchor.prototype);
|
||||
|
||||
});
|
||||
@@ -1,223 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
if (typeof process !== "undefined") {
|
||||
require("amd-loader");
|
||||
}
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var Document = require("./document").Document;
|
||||
var Anchor = require("./anchor").Anchor;
|
||||
var Range = require("./range").Range;
|
||||
var assert = require("./test/assertions");
|
||||
|
||||
module.exports = {
|
||||
|
||||
"test create anchor" : function() {
|
||||
var doc = new Document("juhu");
|
||||
var anchor = new Anchor(doc, 0, 0);
|
||||
|
||||
assert.position(anchor.getPosition(), 0, 0);
|
||||
assert.equal(anchor.getDocument(), doc);
|
||||
},
|
||||
|
||||
"test insert text in same row before cursor should move anchor column": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 4);
|
||||
|
||||
doc.insert({row: 1, column: 1}, "123");
|
||||
assert.position(anchor.getPosition(), 1, 7);
|
||||
},
|
||||
|
||||
"test insert text at anchor should not move anchor when insertRight is true": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 4);
|
||||
anchor.$insertRight = true;
|
||||
|
||||
doc.insert({row: 1, column: 4}, "123");
|
||||
assert.position(anchor.getPosition(), 1, 4);
|
||||
},
|
||||
|
||||
"test insert lines before cursor should move anchor row": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 4);
|
||||
|
||||
doc.insertFullLines(1, ["123", "456"]);
|
||||
assert.position(anchor.getPosition(), 3, 4);
|
||||
},
|
||||
|
||||
"test insert lines at anchor position should move anchor down": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 0);
|
||||
|
||||
doc.insertLines(1, ["line"]);
|
||||
assert.position(anchor.getPosition(), 2, 0);
|
||||
},
|
||||
|
||||
"test insert lines at anchor position should not move anchor down when insertRight is true and column is 0": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 0);
|
||||
anchor.$insertRight = true;
|
||||
|
||||
doc.insertLines(1, ["line"]);
|
||||
assert.position(anchor.getPosition(), 1, 0);
|
||||
},
|
||||
|
||||
"test insert lines at anchor row should move anchor down when column > 0": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 2);
|
||||
anchor.$insertRight = true;
|
||||
|
||||
doc.insertLines(1, ["line"]);
|
||||
assert.position(anchor.getPosition(), 2, 2);
|
||||
},
|
||||
|
||||
"test insert new line before cursor should move anchor column": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 4);
|
||||
|
||||
doc.insertMergedLines({row: 0, column: 0}, ['', '']);
|
||||
assert.position(anchor.getPosition(), 2, 4);
|
||||
},
|
||||
|
||||
"test insert new line in anchor line before anchor should move anchor column and row": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 4);
|
||||
|
||||
doc.insertMergedLines({row: 1, column: 2}, ['', '']);
|
||||
assert.position(anchor.getPosition(), 2, 2);
|
||||
},
|
||||
|
||||
"test delete text in anchor line before anchor should move anchor column": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 4);
|
||||
|
||||
doc.remove(new Range(1, 1, 1, 3));
|
||||
assert.position(anchor.getPosition(), 1, 2);
|
||||
},
|
||||
|
||||
"test remove range which contains the anchor should move the anchor to the start of the range": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 0, 3);
|
||||
|
||||
doc.remove(new Range(0, 1, 1, 3));
|
||||
assert.position(anchor.getPosition(), 0, 1);
|
||||
},
|
||||
|
||||
"test delete character before the anchor should have no effect": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 4);
|
||||
|
||||
doc.remove(new Range(1, 4, 1, 5));
|
||||
assert.position(anchor.getPosition(), 1, 4);
|
||||
},
|
||||
|
||||
"test delete lines in anchor line before anchor should move anchor row": function() {
|
||||
var doc = new Document("juhu\n1\n2\nkinners");
|
||||
var anchor = new Anchor(doc, 3, 4);
|
||||
|
||||
doc.removeFullLines(1, 2);
|
||||
assert.position(anchor.getPosition(), 1, 4);
|
||||
},
|
||||
|
||||
"test remove new line before the cursor": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 4);
|
||||
|
||||
doc.removeNewLine(0);
|
||||
assert.position(anchor.getPosition(), 0, 8);
|
||||
},
|
||||
|
||||
"test delete range which contains the anchor should move anchor to the end of the range": function() {
|
||||
var doc = new Document("juhu\nkinners");
|
||||
var anchor = new Anchor(doc, 1, 4);
|
||||
|
||||
doc.remove(new Range(0, 2, 1, 2));
|
||||
assert.position(anchor.getPosition(), 0, 4);
|
||||
},
|
||||
|
||||
"test delete line which contains the anchor should move anchor to the end of the range": function() {
|
||||
var doc = new Document("juhu\nkinners\n123");
|
||||
var anchor = new Anchor(doc, 1, 5);
|
||||
|
||||
doc.removeFullLines(1, 1);
|
||||
assert.position(anchor.getPosition(), 1, 0);
|
||||
},
|
||||
|
||||
"test remove after the anchor should have no effect": function() {
|
||||
var doc = new Document("juhu\nkinners\n123");
|
||||
var anchor = new Anchor(doc, 1, 2);
|
||||
|
||||
doc.remove(new Range(1, 4, 2, 2));
|
||||
assert.position(anchor.getPosition(), 1, 2);
|
||||
},
|
||||
|
||||
"test anchor changes triggered by document changes should emit change event": function(next) {
|
||||
var doc = new Document("juhu\nkinners\n123");
|
||||
var anchor = new Anchor(doc, 1, 5);
|
||||
|
||||
anchor.on("change", function(e) {
|
||||
assert.position(anchor.getPosition(), 0, 0);
|
||||
next();
|
||||
});
|
||||
|
||||
doc.remove(new Range(0, 0, 2, 1));
|
||||
},
|
||||
|
||||
"test only fire change event if position changes": function() {
|
||||
var doc = new Document("juhu\nkinners\n123");
|
||||
var anchor = new Anchor(doc, 1, 5);
|
||||
|
||||
anchor.on("change", function(e) {
|
||||
assert.fail();
|
||||
});
|
||||
|
||||
doc.remove(new Range(2, 0, 2, 1));
|
||||
},
|
||||
|
||||
"test insert/remove lines at the end of the document": function() {
|
||||
var doc = new Document("juhu\nkinners\n123");
|
||||
var anchor = new Anchor(doc, 2, 4);
|
||||
|
||||
doc.removeFullLines(0, 3);
|
||||
assert.position(anchor.getPosition(), 0, 0);
|
||||
doc.insertFullLines(0, ["a", "b", "c"]);
|
||||
assert.position(anchor.getPosition(), 3, 0);
|
||||
assert.equal(doc.getValue(), "a\nb\nc\n");
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
if (typeof module !== "undefined" && module === require.main) {
|
||||
require("asyncjs").test.testcase(module.exports).exec();
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
function throwDeltaError(delta, errorText){
|
||||
console.log("Invalid Delta:", delta);
|
||||
throw "Invalid Delta: " + errorText;
|
||||
}
|
||||
|
||||
function positionInDocument(docLines, position) {
|
||||
return position.row >= 0 && position.row < docLines.length &&
|
||||
position.column >= 0 && position.column <= docLines[position.row].length;
|
||||
}
|
||||
|
||||
function validateDelta(docLines, delta) {
|
||||
// Validate action string.
|
||||
if (delta.action != "insert" && delta.action != "remove")
|
||||
throwDeltaError(delta, "delta.action must be 'insert' or 'remove'");
|
||||
|
||||
// Validate lines type.
|
||||
if (!(delta.lines instanceof Array))
|
||||
throwDeltaError(delta, "delta.lines must be an Array");
|
||||
|
||||
// Validate range type.
|
||||
if (!delta.start || !delta.end)
|
||||
throwDeltaError(delta, "delta.start/end must be an present");
|
||||
|
||||
// Validate that the start point is contained in the document.
|
||||
var start = delta.start;
|
||||
if (!positionInDocument(docLines, delta.start))
|
||||
throwDeltaError(delta, "delta.start must be contained in document");
|
||||
|
||||
// Validate that the end point is contained in the document (remove deltas only).
|
||||
var end = delta.end;
|
||||
if (delta.action == "remove" && !positionInDocument(docLines, end))
|
||||
throwDeltaError(delta, "delta.end must contained in document for 'remove' actions");
|
||||
|
||||
// Validate that the .range size matches the .lines size.
|
||||
var numRangeRows = end.row - start.row;
|
||||
var numRangeLastLineChars = (end.column - (numRangeRows == 0 ? start.column : 0));
|
||||
if (numRangeRows != delta.lines.length - 1 || delta.lines[numRangeRows].length != numRangeLastLineChars)
|
||||
throwDeltaError(delta, "delta.range must match delta lines");
|
||||
}
|
||||
|
||||
exports.applyDelta = function(docLines, delta, doNotValidate) {
|
||||
// disabled validation since it breaks autocompletion popup
|
||||
// if (!doNotValidate)
|
||||
// validateDelta(docLines, delta);
|
||||
|
||||
var row = delta.start.row;
|
||||
var startColumn = delta.start.column;
|
||||
var line = docLines[row] || "";
|
||||
switch (delta.action) {
|
||||
case "insert":
|
||||
var lines = delta.lines;
|
||||
if (lines.length === 1) {
|
||||
docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn);
|
||||
} else {
|
||||
var args = [row, 1].concat(delta.lines);
|
||||
docLines.splice.apply(docLines, args);
|
||||
docLines[row] = line.substring(0, startColumn) + docLines[row];
|
||||
docLines[row + delta.lines.length - 1] += line.substring(startColumn);
|
||||
}
|
||||
break;
|
||||
case "remove":
|
||||
var endColumn = delta.end.column;
|
||||
var endRow = delta.end.row;
|
||||
if (row === endRow) {
|
||||
docLines[row] = line.substring(0, startColumn) + line.substring(endColumn);
|
||||
} else {
|
||||
docLines.splice(
|
||||
row, endRow - row + 1,
|
||||
line.substring(0, startColumn) + docLines[endRow].substring(endColumn)
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,562 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2012, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var HashHandler = require("./keyboard/hash_handler").HashHandler;
|
||||
var AcePopup = require("./autocomplete/popup").AcePopup;
|
||||
var util = require("./autocomplete/util");
|
||||
var lang = require("./lib/lang");
|
||||
var dom = require("./lib/dom");
|
||||
var snippetManager = require("./snippets").snippetManager;
|
||||
var config = require("./config");
|
||||
|
||||
var Autocomplete = function() {
|
||||
this.autoInsert = false;
|
||||
this.autoSelect = true;
|
||||
this.exactMatch = false;
|
||||
this.gatherCompletionsId = 0;
|
||||
this.keyboardHandler = new HashHandler();
|
||||
this.keyboardHandler.bindKeys(this.commands);
|
||||
|
||||
this.blurListener = this.blurListener.bind(this);
|
||||
this.changeListener = this.changeListener.bind(this);
|
||||
this.mousedownListener = this.mousedownListener.bind(this);
|
||||
this.mousewheelListener = this.mousewheelListener.bind(this);
|
||||
|
||||
this.changeTimer = lang.delayedCall(function() {
|
||||
this.updateCompletions(true);
|
||||
}.bind(this));
|
||||
|
||||
this.tooltipTimer = lang.delayedCall(this.updateDocTooltip.bind(this), 50);
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
this.$init = function() {
|
||||
this.popup = new AcePopup(document.body || document.documentElement);
|
||||
this.popup.on("click", function(e) {
|
||||
this.insertMatch();
|
||||
e.stop();
|
||||
}.bind(this));
|
||||
this.popup.focus = this.editor.focus.bind(this.editor);
|
||||
this.popup.on("show", this.tooltipTimer.bind(null, null));
|
||||
this.popup.on("select", this.tooltipTimer.bind(null, null));
|
||||
this.popup.on("changeHoverMarker", this.tooltipTimer.bind(null, null));
|
||||
return this.popup;
|
||||
};
|
||||
|
||||
this.getPopup = function() {
|
||||
return this.popup || this.$init();
|
||||
};
|
||||
|
||||
this.openPopup = function(editor, prefix, keepPopupPosition) {
|
||||
if (!this.popup)
|
||||
this.$init();
|
||||
|
||||
this.popup.autoSelect = this.autoSelect;
|
||||
|
||||
this.popup.setData(this.completions.filtered, this.completions.filterText);
|
||||
|
||||
editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
|
||||
|
||||
var renderer = editor.renderer;
|
||||
this.popup.setRow(this.autoSelect ? 0 : -1);
|
||||
if (!keepPopupPosition) {
|
||||
this.popup.setTheme(editor.getTheme());
|
||||
this.popup.setFontSize(editor.getFontSize());
|
||||
|
||||
var lineHeight = renderer.layerConfig.lineHeight;
|
||||
|
||||
var pos = renderer.$cursorLayer.getPixelPosition(this.base, true);
|
||||
pos.left -= this.popup.getTextLeftOffset();
|
||||
|
||||
var rect = editor.container.getBoundingClientRect();
|
||||
pos.top += rect.top - renderer.layerConfig.offset;
|
||||
pos.left += rect.left - editor.renderer.scrollLeft;
|
||||
pos.left += renderer.gutterWidth;
|
||||
|
||||
this.popup.show(pos, lineHeight);
|
||||
} else if (keepPopupPosition && !prefix) {
|
||||
this.detach();
|
||||
}
|
||||
};
|
||||
|
||||
this.detach = function() {
|
||||
this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler);
|
||||
this.editor.off("changeSelection", this.changeListener);
|
||||
this.editor.off("blur", this.blurListener);
|
||||
this.editor.off("mousedown", this.mousedownListener);
|
||||
this.editor.off("mousewheel", this.mousewheelListener);
|
||||
this.changeTimer.cancel();
|
||||
this.hideDocTooltip();
|
||||
|
||||
this.gatherCompletionsId += 1;
|
||||
if (this.popup && this.popup.isOpen)
|
||||
this.popup.hide();
|
||||
|
||||
if (this.base)
|
||||
this.base.detach();
|
||||
this.activated = false;
|
||||
this.completions = this.base = null;
|
||||
};
|
||||
|
||||
this.changeListener = function(e) {
|
||||
var cursor = this.editor.selection.lead;
|
||||
if (cursor.row != this.base.row || cursor.column < this.base.column) {
|
||||
this.detach();
|
||||
}
|
||||
if (this.activated)
|
||||
this.changeTimer.schedule();
|
||||
else
|
||||
this.detach();
|
||||
};
|
||||
|
||||
this.blurListener = function(e) {
|
||||
// we have to check if activeElement is a child of popup because
|
||||
// on IE preventDefault doesn't stop scrollbar from being focussed
|
||||
var el = document.activeElement;
|
||||
var text = this.editor.textInput.getElement();
|
||||
var fromTooltip = e.relatedTarget && this.tooltipNode && this.tooltipNode.contains(e.relatedTarget);
|
||||
var container = this.popup && this.popup.container;
|
||||
if (el != text && el.parentNode != container && !fromTooltip
|
||||
&& el != this.tooltipNode && e.relatedTarget != text
|
||||
) {
|
||||
this.detach();
|
||||
}
|
||||
};
|
||||
|
||||
this.mousedownListener = function(e) {
|
||||
this.detach();
|
||||
};
|
||||
|
||||
this.mousewheelListener = function(e) {
|
||||
this.detach();
|
||||
};
|
||||
|
||||
this.goTo = function(where) {
|
||||
this.popup.goTo(where);
|
||||
};
|
||||
|
||||
this.insertMatch = function(data, options) {
|
||||
if (!data)
|
||||
data = this.popup.getData(this.popup.getRow());
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
if (data.completer && data.completer.insertMatch) {
|
||||
data.completer.insertMatch(this.editor, data);
|
||||
} else {
|
||||
// TODO add support for options.deleteSuffix
|
||||
if (this.completions.filterText) {
|
||||
var ranges = this.editor.selection.getAllRanges();
|
||||
for (var i = 0, range; range = ranges[i]; i++) {
|
||||
range.start.column -= this.completions.filterText.length;
|
||||
this.editor.session.remove(range);
|
||||
}
|
||||
}
|
||||
if (data.snippet)
|
||||
snippetManager.insertSnippet(this.editor, data.snippet);
|
||||
else
|
||||
this.editor.execCommand("insertstring", data.value || data);
|
||||
}
|
||||
this.detach();
|
||||
};
|
||||
|
||||
|
||||
this.commands = {
|
||||
"Up": function(editor) { editor.completer.goTo("up"); },
|
||||
"Down": function(editor) { editor.completer.goTo("down"); },
|
||||
"Ctrl-Up|Ctrl-Home": function(editor) { editor.completer.goTo("start"); },
|
||||
"Ctrl-Down|Ctrl-End": function(editor) { editor.completer.goTo("end"); },
|
||||
|
||||
"Esc": function(editor) { editor.completer.detach(); },
|
||||
"Return": function(editor) { return editor.completer.insertMatch(); },
|
||||
"Shift-Return": function(editor) { editor.completer.insertMatch(null, {deleteSuffix: true}); },
|
||||
"Tab": function(editor) {
|
||||
var result = editor.completer.insertMatch();
|
||||
if (!result && !editor.tabstopManager)
|
||||
editor.completer.goTo("down");
|
||||
else
|
||||
return result;
|
||||
},
|
||||
|
||||
"PageUp": function(editor) { editor.completer.popup.gotoPageUp(); },
|
||||
"PageDown": function(editor) { editor.completer.popup.gotoPageDown(); }
|
||||
};
|
||||
|
||||
this.gatherCompletions = function(editor, callback) {
|
||||
var session = editor.getSession();
|
||||
var pos = editor.getCursorPosition();
|
||||
|
||||
var prefix = util.getCompletionPrefix(editor);
|
||||
|
||||
this.base = session.doc.createAnchor(pos.row, pos.column - prefix.length);
|
||||
this.base.$insertRight = true;
|
||||
|
||||
var matches = [];
|
||||
var total = editor.completers.length;
|
||||
editor.completers.forEach(function(completer, i) {
|
||||
completer.getCompletions(editor, session, pos, prefix, function(err, results) {
|
||||
if (!err && results)
|
||||
matches = matches.concat(results);
|
||||
// Fetch prefix again, because they may have changed by now
|
||||
callback(null, {
|
||||
prefix: util.getCompletionPrefix(editor),
|
||||
matches: matches,
|
||||
finished: (--total === 0)
|
||||
});
|
||||
});
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
this.showPopup = function(editor, options) {
|
||||
if (this.editor)
|
||||
this.detach();
|
||||
|
||||
this.activated = true;
|
||||
|
||||
this.editor = editor;
|
||||
if (editor.completer != this) {
|
||||
if (editor.completer)
|
||||
editor.completer.detach();
|
||||
editor.completer = this;
|
||||
}
|
||||
|
||||
editor.on("changeSelection", this.changeListener);
|
||||
editor.on("blur", this.blurListener);
|
||||
editor.on("mousedown", this.mousedownListener);
|
||||
editor.on("mousewheel", this.mousewheelListener);
|
||||
|
||||
this.updateCompletions(false, options);
|
||||
};
|
||||
|
||||
this.updateCompletions = function(keepPopupPosition, options) {
|
||||
if (keepPopupPosition && this.base && this.completions) {
|
||||
var pos = this.editor.getCursorPosition();
|
||||
var prefix = this.editor.session.getTextRange({start: this.base, end: pos});
|
||||
if (prefix == this.completions.filterText)
|
||||
return;
|
||||
this.completions.setFilter(prefix);
|
||||
if (!this.completions.filtered.length)
|
||||
return this.detach();
|
||||
if (this.completions.filtered.length == 1
|
||||
&& this.completions.filtered[0].value == prefix
|
||||
&& !this.completions.filtered[0].snippet)
|
||||
return this.detach();
|
||||
this.openPopup(this.editor, prefix, keepPopupPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options && options.matches) {
|
||||
var pos = this.editor.getSelectionRange().start;
|
||||
this.base = this.editor.session.doc.createAnchor(pos.row, pos.column);
|
||||
this.base.$insertRight = true;
|
||||
this.completions = new FilteredList(options.matches);
|
||||
return this.openPopup(this.editor, "", keepPopupPosition);
|
||||
}
|
||||
|
||||
// Save current gatherCompletions session, session is close when a match is insert
|
||||
var _id = this.gatherCompletionsId;
|
||||
this.gatherCompletions(this.editor, function(err, results) {
|
||||
// Only detach if result gathering is finished
|
||||
var detachIfFinished = function() {
|
||||
if (!results.finished) return;
|
||||
return this.detach();
|
||||
}.bind(this);
|
||||
|
||||
var prefix = results.prefix;
|
||||
var matches = results && results.matches;
|
||||
|
||||
if (!matches || !matches.length)
|
||||
return detachIfFinished();
|
||||
|
||||
// Wrong prefix or wrong session -> ignore
|
||||
if (prefix.indexOf(results.prefix) !== 0 || _id != this.gatherCompletionsId)
|
||||
return;
|
||||
|
||||
this.completions = new FilteredList(matches);
|
||||
|
||||
if (this.exactMatch)
|
||||
this.completions.exactMatch = true;
|
||||
|
||||
this.completions.setFilter(prefix);
|
||||
var filtered = this.completions.filtered;
|
||||
|
||||
// No results
|
||||
if (!filtered.length)
|
||||
return detachIfFinished();
|
||||
|
||||
// One result equals to the prefix
|
||||
if (filtered.length == 1 && filtered[0].value == prefix && !filtered[0].snippet)
|
||||
return detachIfFinished();
|
||||
|
||||
// Autoinsert if one result
|
||||
if (this.autoInsert && filtered.length == 1 && results.finished)
|
||||
return this.insertMatch(filtered[0]);
|
||||
|
||||
this.openPopup(this.editor, prefix, keepPopupPosition);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
this.cancelContextMenu = function() {
|
||||
this.editor.$mouseHandler.cancelContextMenu();
|
||||
};
|
||||
|
||||
this.updateDocTooltip = function() {
|
||||
var popup = this.popup;
|
||||
var all = popup.data;
|
||||
var selected = all && (all[popup.getHoveredRow()] || all[popup.getRow()]);
|
||||
var doc = null;
|
||||
if (!selected || !this.editor || !this.popup.isOpen)
|
||||
return this.hideDocTooltip();
|
||||
this.editor.completers.some(function(completer) {
|
||||
if (completer.getDocTooltip)
|
||||
doc = completer.getDocTooltip(selected);
|
||||
return doc;
|
||||
});
|
||||
if (!doc && typeof selected != "string")
|
||||
doc = selected;
|
||||
|
||||
if (typeof doc == "string")
|
||||
doc = {docText: doc};
|
||||
if (!doc || !(doc.docHTML || doc.docText))
|
||||
return this.hideDocTooltip();
|
||||
this.showDocTooltip(doc);
|
||||
};
|
||||
|
||||
this.showDocTooltip = function(item) {
|
||||
if (!this.tooltipNode) {
|
||||
this.tooltipNode = dom.createElement("div");
|
||||
this.tooltipNode.className = "ace_tooltip ace_doc-tooltip";
|
||||
this.tooltipNode.style.margin = 0;
|
||||
this.tooltipNode.style.pointerEvents = "auto";
|
||||
this.tooltipNode.tabIndex = -1;
|
||||
this.tooltipNode.onblur = this.blurListener.bind(this);
|
||||
this.tooltipNode.onclick = this.onTooltipClick.bind(this);
|
||||
}
|
||||
|
||||
var tooltipNode = this.tooltipNode;
|
||||
if (item.docHTML) {
|
||||
tooltipNode.innerHTML = item.docHTML;
|
||||
} else if (item.docText) {
|
||||
tooltipNode.textContent = item.docText;
|
||||
}
|
||||
|
||||
if (!tooltipNode.parentNode)
|
||||
document.body.appendChild(tooltipNode);
|
||||
var popup = this.popup;
|
||||
var rect = popup.container.getBoundingClientRect();
|
||||
tooltipNode.style.top = popup.container.style.top;
|
||||
tooltipNode.style.bottom = popup.container.style.bottom;
|
||||
|
||||
tooltipNode.style.display = "block";
|
||||
if (window.innerWidth - rect.right < 320) {
|
||||
if (rect.left < 320) {
|
||||
if(popup.isTopdown) {
|
||||
tooltipNode.style.top = rect.bottom + "px";
|
||||
tooltipNode.style.left = rect.left + "px";
|
||||
tooltipNode.style.right = "";
|
||||
tooltipNode.style.bottom = "";
|
||||
} else {
|
||||
tooltipNode.style.top = popup.container.offsetTop - tooltipNode.offsetHeight + "px";
|
||||
tooltipNode.style.left = rect.left + "px";
|
||||
tooltipNode.style.right = "";
|
||||
tooltipNode.style.bottom = "";
|
||||
}
|
||||
} else {
|
||||
tooltipNode.style.right = window.innerWidth - rect.left + "px";
|
||||
tooltipNode.style.left = "";
|
||||
}
|
||||
} else {
|
||||
tooltipNode.style.left = (rect.right + 1) + "px";
|
||||
tooltipNode.style.right = "";
|
||||
}
|
||||
};
|
||||
|
||||
this.hideDocTooltip = function() {
|
||||
this.tooltipTimer.cancel();
|
||||
if (!this.tooltipNode) return;
|
||||
var el = this.tooltipNode;
|
||||
if (!this.editor.isFocused() && document.activeElement == el)
|
||||
this.editor.focus();
|
||||
this.tooltipNode = null;
|
||||
if (el.parentNode)
|
||||
el.parentNode.removeChild(el);
|
||||
};
|
||||
|
||||
this.onTooltipClick = function(e) {
|
||||
var a = e.target;
|
||||
while (a && a != this.tooltipNode) {
|
||||
if (a.nodeName == "A" && a.href) {
|
||||
a.rel = "noreferrer";
|
||||
a.target = "_blank";
|
||||
break;
|
||||
}
|
||||
a = a.parentNode;
|
||||
}
|
||||
};
|
||||
|
||||
this.destroy = function() {
|
||||
this.detach();
|
||||
if (this.popup) {
|
||||
this.popup.destroy();
|
||||
var el = this.popup.container;
|
||||
if (el && el.parentNode)
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
if (this.editor && this.editor.completer == this)
|
||||
this.editor.completer == null;
|
||||
this.popup = null;
|
||||
};
|
||||
|
||||
}).call(Autocomplete.prototype);
|
||||
|
||||
|
||||
Autocomplete.for = function(editor) {
|
||||
if (editor.completer) {
|
||||
return editor.completer;
|
||||
}
|
||||
if (config.get("sharedPopups")) {
|
||||
if (!Autocomplete.$shared)
|
||||
Autocomplete.$sharedInstance = new Autocomplete();
|
||||
editor.completer = Autocomplete.$sharedInstance;
|
||||
} else {
|
||||
editor.completer = new Autocomplete();
|
||||
editor.once("destroy", function(e, editor) {
|
||||
editor.completer.destroy();
|
||||
});
|
||||
}
|
||||
return editor.completer;
|
||||
};
|
||||
|
||||
Autocomplete.startCommand = {
|
||||
name: "startAutocomplete",
|
||||
exec: function(editor, options) {
|
||||
var completer = Autocomplete.for(editor);
|
||||
completer.autoInsert = false;
|
||||
completer.autoSelect = true;
|
||||
completer.showPopup(editor, options);
|
||||
// prevent ctrl-space opening context menu on firefox on mac
|
||||
completer.cancelContextMenu();
|
||||
},
|
||||
bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space"
|
||||
};
|
||||
|
||||
var FilteredList = function(array, filterText) {
|
||||
this.all = array;
|
||||
this.filtered = array;
|
||||
this.filterText = filterText || "";
|
||||
this.exactMatch = false;
|
||||
};
|
||||
(function(){
|
||||
this.setFilter = function(str) {
|
||||
if (str.length > this.filterText && str.lastIndexOf(this.filterText, 0) === 0)
|
||||
var matches = this.filtered;
|
||||
else
|
||||
var matches = this.all;
|
||||
|
||||
this.filterText = str;
|
||||
matches = this.filterCompletions(matches, this.filterText);
|
||||
matches = matches.sort(function(a, b) {
|
||||
return b.exactMatch - a.exactMatch || b.$score - a.$score
|
||||
|| (a.caption || a.value).localeCompare(b.caption || b.value);
|
||||
});
|
||||
|
||||
// make unique
|
||||
var prev = null;
|
||||
matches = matches.filter(function(item){
|
||||
var caption = item.snippet || item.caption || item.value;
|
||||
if (caption === prev) return false;
|
||||
prev = caption;
|
||||
return true;
|
||||
});
|
||||
|
||||
this.filtered = matches;
|
||||
};
|
||||
this.filterCompletions = function(items, needle) {
|
||||
var results = [];
|
||||
var upper = needle.toUpperCase();
|
||||
var lower = needle.toLowerCase();
|
||||
loop: for (var i = 0, item; item = items[i]; i++) {
|
||||
var caption = item.caption || item.value || item.snippet;
|
||||
if (!caption) continue;
|
||||
var lastIndex = -1;
|
||||
var matchMask = 0;
|
||||
var penalty = 0;
|
||||
var index, distance;
|
||||
|
||||
if (this.exactMatch) {
|
||||
if (needle !== caption.substr(0, needle.length))
|
||||
continue loop;
|
||||
} else {
|
||||
/**
|
||||
* It is for situation then, for example, we find some like 'tab' in item.value="Check the table"
|
||||
* and want to see "Check the TABle" but see "Check The tABle".
|
||||
*/
|
||||
var fullMatchIndex = caption.toLowerCase().indexOf(lower);
|
||||
if (fullMatchIndex > -1) {
|
||||
penalty = fullMatchIndex;
|
||||
} else {
|
||||
// caption char iteration is faster in Chrome but slower in Firefox, so lets use indexOf
|
||||
for (var j = 0; j < needle.length; j++) {
|
||||
// TODO add penalty on case mismatch
|
||||
var i1 = caption.indexOf(lower[j], lastIndex + 1);
|
||||
var i2 = caption.indexOf(upper[j], lastIndex + 1);
|
||||
index = (i1 >= 0) ? ((i2 < 0 || i1 < i2) ? i1 : i2) : i2;
|
||||
if (index < 0)
|
||||
continue loop;
|
||||
distance = index - lastIndex - 1;
|
||||
if (distance > 0) {
|
||||
// first char mismatch should be more sensitive
|
||||
if (lastIndex === -1)
|
||||
penalty += 10;
|
||||
penalty += distance;
|
||||
matchMask = matchMask | (1 << j);
|
||||
}
|
||||
lastIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
item.matchMask = matchMask;
|
||||
item.exactMatch = penalty ? 0 : 1;
|
||||
item.$score = (item.score || 0) - penalty;
|
||||
results.push(item);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
}).call(FilteredList.prototype);
|
||||
|
||||
exports.Autocomplete = Autocomplete;
|
||||
exports.FilteredList = FilteredList;
|
||||
|
||||
});
|
||||
@@ -1,376 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2012, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var Renderer = require("../virtual_renderer").VirtualRenderer;
|
||||
var Editor = require("../editor").Editor;
|
||||
var Range = require("../range").Range;
|
||||
var event = require("../lib/event");
|
||||
var lang = require("../lib/lang");
|
||||
var dom = require("../lib/dom");
|
||||
|
||||
var $singleLineEditor = function(el) {
|
||||
var renderer = new Renderer(el);
|
||||
|
||||
renderer.$maxLines = 4;
|
||||
|
||||
var editor = new Editor(renderer);
|
||||
|
||||
editor.setHighlightActiveLine(false);
|
||||
editor.setShowPrintMargin(false);
|
||||
editor.renderer.setShowGutter(false);
|
||||
editor.renderer.setHighlightGutterLine(false);
|
||||
|
||||
editor.$mouseHandler.$focusTimeout = 0;
|
||||
editor.$highlightTagPending = true;
|
||||
|
||||
return editor;
|
||||
};
|
||||
|
||||
var AcePopup = function(parentNode) {
|
||||
var el = dom.createElement("div");
|
||||
var popup = new $singleLineEditor(el);
|
||||
|
||||
if (parentNode)
|
||||
parentNode.appendChild(el);
|
||||
el.style.display = "none";
|
||||
popup.renderer.content.style.cursor = "default";
|
||||
popup.renderer.setStyle("ace_autocomplete");
|
||||
|
||||
popup.setOption("displayIndentGuides", false);
|
||||
popup.setOption("dragDelay", 150);
|
||||
|
||||
var noop = function(){};
|
||||
|
||||
popup.focus = noop;
|
||||
popup.$isFocused = true;
|
||||
|
||||
popup.renderer.$cursorLayer.restartTimer = noop;
|
||||
popup.renderer.$cursorLayer.element.style.opacity = 0;
|
||||
|
||||
popup.renderer.$maxLines = 8;
|
||||
popup.renderer.$keepTextAreaAtCursor = false;
|
||||
|
||||
popup.setHighlightActiveLine(false);
|
||||
// set default highlight color
|
||||
popup.session.highlight("");
|
||||
popup.session.$searchHighlight.clazz = "ace_highlight-marker";
|
||||
|
||||
popup.on("mousedown", function(e) {
|
||||
var pos = e.getDocumentPosition();
|
||||
popup.selection.moveToPosition(pos);
|
||||
selectionMarker.start.row = selectionMarker.end.row = pos.row;
|
||||
e.stop();
|
||||
});
|
||||
|
||||
var lastMouseEvent;
|
||||
var hoverMarker = new Range(-1,0,-1,Infinity);
|
||||
var selectionMarker = new Range(-1,0,-1,Infinity);
|
||||
selectionMarker.id = popup.session.addMarker(selectionMarker, "ace_active-line", "fullLine");
|
||||
popup.setSelectOnHover = function(val) {
|
||||
if (!val) {
|
||||
hoverMarker.id = popup.session.addMarker(hoverMarker, "ace_line-hover", "fullLine");
|
||||
} else if (hoverMarker.id) {
|
||||
popup.session.removeMarker(hoverMarker.id);
|
||||
hoverMarker.id = null;
|
||||
}
|
||||
};
|
||||
popup.setSelectOnHover(false);
|
||||
popup.on("mousemove", function(e) {
|
||||
if (!lastMouseEvent) {
|
||||
lastMouseEvent = e;
|
||||
return;
|
||||
}
|
||||
if (lastMouseEvent.x == e.x && lastMouseEvent.y == e.y) {
|
||||
return;
|
||||
}
|
||||
lastMouseEvent = e;
|
||||
lastMouseEvent.scrollTop = popup.renderer.scrollTop;
|
||||
var row = lastMouseEvent.getDocumentPosition().row;
|
||||
if (hoverMarker.start.row != row) {
|
||||
if (!hoverMarker.id)
|
||||
popup.setRow(row);
|
||||
setHoverMarker(row);
|
||||
}
|
||||
});
|
||||
popup.renderer.on("beforeRender", function() {
|
||||
if (lastMouseEvent && hoverMarker.start.row != -1) {
|
||||
lastMouseEvent.$pos = null;
|
||||
var row = lastMouseEvent.getDocumentPosition().row;
|
||||
if (!hoverMarker.id)
|
||||
popup.setRow(row);
|
||||
setHoverMarker(row, true);
|
||||
}
|
||||
});
|
||||
popup.renderer.on("afterRender", function() {
|
||||
var row = popup.getRow();
|
||||
var t = popup.renderer.$textLayer;
|
||||
var selected = t.element.childNodes[row - t.config.firstRow];
|
||||
if (selected !== t.selectedNode && t.selectedNode)
|
||||
dom.removeCssClass(t.selectedNode, "ace_selected");
|
||||
t.selectedNode = selected;
|
||||
if (selected)
|
||||
dom.addCssClass(selected, "ace_selected");
|
||||
});
|
||||
var hideHoverMarker = function() { setHoverMarker(-1); };
|
||||
var setHoverMarker = function(row, suppressRedraw) {
|
||||
if (row !== hoverMarker.start.row) {
|
||||
hoverMarker.start.row = hoverMarker.end.row = row;
|
||||
if (!suppressRedraw)
|
||||
popup.session._emit("changeBackMarker");
|
||||
popup._emit("changeHoverMarker");
|
||||
}
|
||||
};
|
||||
popup.getHoveredRow = function() {
|
||||
return hoverMarker.start.row;
|
||||
};
|
||||
|
||||
event.addListener(popup.container, "mouseout", hideHoverMarker);
|
||||
popup.on("hide", hideHoverMarker);
|
||||
popup.on("changeSelection", hideHoverMarker);
|
||||
|
||||
popup.session.doc.getLength = function() {
|
||||
return popup.data.length;
|
||||
};
|
||||
popup.session.doc.getLine = function(i) {
|
||||
var data = popup.data[i];
|
||||
if (typeof data == "string")
|
||||
return data;
|
||||
return (data && data.value) || "";
|
||||
};
|
||||
|
||||
var bgTokenizer = popup.session.bgTokenizer;
|
||||
bgTokenizer.$tokenizeRow = function(row) {
|
||||
var data = popup.data[row];
|
||||
var tokens = [];
|
||||
if (!data)
|
||||
return tokens;
|
||||
if (typeof data == "string")
|
||||
data = {value: data};
|
||||
var caption = data.caption || data.value || data.name;
|
||||
|
||||
function addToken(value, className) {
|
||||
value && tokens.push({
|
||||
type: (data.className || "") + (className || ""),
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
var lower = caption.toLowerCase();
|
||||
var filterText = (popup.filterText || "").toLowerCase();
|
||||
var lastIndex = 0;
|
||||
var lastI = 0;
|
||||
for (var i = 0; i <= filterText.length; i++) {
|
||||
if (i != lastI && (data.matchMask & (1 << i) || i == filterText.length)) {
|
||||
var sub = filterText.slice(lastI, i);
|
||||
lastI = i;
|
||||
var index = lower.indexOf(sub, lastIndex);
|
||||
if (index == -1) continue;
|
||||
addToken(caption.slice(lastIndex, index), "");
|
||||
lastIndex = index + sub.length;
|
||||
addToken(caption.slice(index, lastIndex), "completion-highlight");
|
||||
}
|
||||
}
|
||||
addToken(caption.slice(lastIndex, caption.length), "");
|
||||
|
||||
if (data.meta)
|
||||
tokens.push({type: "completion-meta", value: data.meta});
|
||||
if (data.message)
|
||||
tokens.push({type: "completion-message", value: data.message});
|
||||
|
||||
return tokens;
|
||||
};
|
||||
bgTokenizer.$updateOnChange = noop;
|
||||
bgTokenizer.start = noop;
|
||||
|
||||
popup.session.$computeWidth = function() {
|
||||
return this.screenWidth = 0;
|
||||
};
|
||||
|
||||
// public
|
||||
popup.isOpen = false;
|
||||
popup.isTopdown = false;
|
||||
popup.autoSelect = true;
|
||||
popup.filterText = "";
|
||||
|
||||
popup.data = [];
|
||||
popup.setData = function(list, filterText) {
|
||||
popup.filterText = filterText || "";
|
||||
popup.setValue(lang.stringRepeat("\n", list.length), -1);
|
||||
popup.data = list || [];
|
||||
popup.setRow(0);
|
||||
};
|
||||
popup.getData = function(row) {
|
||||
return popup.data[row];
|
||||
};
|
||||
|
||||
popup.getRow = function() {
|
||||
return selectionMarker.start.row;
|
||||
};
|
||||
popup.setRow = function(line) {
|
||||
line = Math.max(this.autoSelect ? 0 : -1, Math.min(this.data.length, line));
|
||||
if (selectionMarker.start.row != line) {
|
||||
popup.selection.clearSelection();
|
||||
selectionMarker.start.row = selectionMarker.end.row = line || 0;
|
||||
popup.session._emit("changeBackMarker");
|
||||
popup.moveCursorTo(line || 0, 0);
|
||||
if (popup.isOpen)
|
||||
popup._signal("select");
|
||||
}
|
||||
};
|
||||
|
||||
popup.on("changeSelection", function() {
|
||||
if (popup.isOpen)
|
||||
popup.setRow(popup.selection.lead.row);
|
||||
popup.renderer.scrollCursorIntoView();
|
||||
});
|
||||
|
||||
popup.hide = function() {
|
||||
this.container.style.display = "none";
|
||||
this._signal("hide");
|
||||
popup.isOpen = false;
|
||||
};
|
||||
popup.show = function(pos, lineHeight, topdownOnly) {
|
||||
var el = this.container;
|
||||
var screenHeight = window.innerHeight;
|
||||
var screenWidth = window.innerWidth;
|
||||
var renderer = this.renderer;
|
||||
// var maxLines = Math.min(renderer.$maxLines, this.session.getLength());
|
||||
var maxH = renderer.$maxLines * lineHeight * 1.4;
|
||||
var top = pos.top + this.$borderSize;
|
||||
var allowTopdown = top > screenHeight / 2 && !topdownOnly;
|
||||
if (allowTopdown && top + lineHeight + maxH > screenHeight) {
|
||||
renderer.$maxPixelHeight = top - 2 * this.$borderSize;
|
||||
el.style.top = "";
|
||||
el.style.bottom = screenHeight - top + "px";
|
||||
popup.isTopdown = false;
|
||||
} else {
|
||||
top += lineHeight;
|
||||
renderer.$maxPixelHeight = screenHeight - top - 0.2 * lineHeight;
|
||||
el.style.top = top + "px";
|
||||
el.style.bottom = "";
|
||||
popup.isTopdown = true;
|
||||
}
|
||||
|
||||
el.style.display = "";
|
||||
|
||||
var left = pos.left;
|
||||
if (left + el.offsetWidth > screenWidth)
|
||||
left = screenWidth - el.offsetWidth;
|
||||
|
||||
el.style.left = left + "px";
|
||||
|
||||
this._signal("show");
|
||||
lastMouseEvent = null;
|
||||
popup.isOpen = true;
|
||||
};
|
||||
|
||||
popup.goTo = function(where) {
|
||||
var row = this.getRow();
|
||||
var max = this.session.getLength() - 1;
|
||||
|
||||
switch(where) {
|
||||
case "up": row = row <= 0 ? max : row - 1; break;
|
||||
case "down": row = row >= max ? -1 : row + 1; break;
|
||||
case "start": row = 0; break;
|
||||
case "end": row = max; break;
|
||||
}
|
||||
|
||||
this.setRow(row);
|
||||
};
|
||||
|
||||
|
||||
popup.getTextLeftOffset = function() {
|
||||
return this.$borderSize + this.renderer.$padding + this.$imageSize;
|
||||
};
|
||||
|
||||
popup.$imageSize = 0;
|
||||
popup.$borderSize = 1;
|
||||
|
||||
return popup;
|
||||
};
|
||||
|
||||
dom.importCssString("\
|
||||
.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {\
|
||||
background-color: #CAD6FA;\
|
||||
z-index: 1;\
|
||||
}\
|
||||
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {\
|
||||
background-color: #3a674e;\
|
||||
}\
|
||||
.ace_editor.ace_autocomplete .ace_line-hover {\
|
||||
border: 1px solid #abbffe;\
|
||||
margin-top: -1px;\
|
||||
background: rgba(233,233,253,0.4);\
|
||||
position: absolute;\
|
||||
z-index: 2;\
|
||||
}\
|
||||
.ace_dark.ace_editor.ace_autocomplete .ace_line-hover {\
|
||||
border: 1px solid rgba(109, 150, 13, 0.8);\
|
||||
background: rgba(58, 103, 78, 0.62);\
|
||||
}\
|
||||
.ace_completion-meta {\
|
||||
opacity: 0.5;\
|
||||
margin: 0.9em;\
|
||||
}\
|
||||
.ace_completion-message {\
|
||||
color: blue;\
|
||||
}\
|
||||
.ace_editor.ace_autocomplete .ace_completion-highlight{\
|
||||
color: #2d69c7;\
|
||||
}\
|
||||
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight{\
|
||||
color: #93ca12;\
|
||||
}\
|
||||
.ace_editor.ace_autocomplete {\
|
||||
width: 300px;\
|
||||
z-index: 200000;\
|
||||
border: 1px lightgray solid;\
|
||||
position: fixed;\
|
||||
box-shadow: 2px 3px 5px rgba(0,0,0,.2);\
|
||||
line-height: 1.4;\
|
||||
background: #fefefe;\
|
||||
color: #111;\
|
||||
}\
|
||||
.ace_dark.ace_editor.ace_autocomplete {\
|
||||
border: 1px #484747 solid;\
|
||||
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);\
|
||||
line-height: 1.4;\
|
||||
background: #25282c;\
|
||||
color: #c1c1c1;\
|
||||
}", "autocompletion.css");
|
||||
|
||||
exports.AcePopup = AcePopup;
|
||||
exports.$singleLineEditor = $singleLineEditor;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2012, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
var Range = require("../range").Range;
|
||||
|
||||
var splitRegex = /[^a-zA-Z_0-9\$\-\u00C0-\u1FFF\u2C00-\uD7FF\w]+/;
|
||||
|
||||
function getWordIndex(doc, pos) {
|
||||
var textBefore = doc.getTextRange(Range.fromPoints({row: 0, column:0}, pos));
|
||||
return textBefore.split(splitRegex).length - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a distance analysis of the word `prefix` at position `pos` in `doc`.
|
||||
* @return Map
|
||||
*/
|
||||
function wordDistance(doc, pos) {
|
||||
var prefixPos = getWordIndex(doc, pos);
|
||||
var words = doc.getValue().split(splitRegex);
|
||||
var wordScores = Object.create(null);
|
||||
|
||||
var currentWord = words[prefixPos];
|
||||
|
||||
words.forEach(function(word, idx) {
|
||||
if (!word || word === currentWord) return;
|
||||
|
||||
var distance = Math.abs(prefixPos - idx);
|
||||
var score = words.length - distance;
|
||||
if (wordScores[word]) {
|
||||
wordScores[word] = Math.max(score, wordScores[word]);
|
||||
} else {
|
||||
wordScores[word] = score;
|
||||
}
|
||||
});
|
||||
return wordScores;
|
||||
}
|
||||
|
||||
exports.getCompletions = function(editor, session, pos, prefix, callback) {
|
||||
var wordScore = wordDistance(session, pos);
|
||||
var wordList = Object.keys(wordScore);
|
||||
callback(null, wordList.map(function(word) {
|
||||
return {
|
||||
caption: word,
|
||||
value: word,
|
||||
score: wordScore[word],
|
||||
meta: "local"
|
||||
};
|
||||
}));
|
||||
};
|
||||
});
|
||||
@@ -1,89 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2012, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
exports.parForEach = function(array, fn, callback) {
|
||||
var completed = 0;
|
||||
var arLength = array.length;
|
||||
if (arLength === 0)
|
||||
callback();
|
||||
for (var i = 0; i < arLength; i++) {
|
||||
fn(array[i], function(result, err) {
|
||||
completed++;
|
||||
if (completed === arLength)
|
||||
callback(result, err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var ID_REGEX = /[a-zA-Z_0-9\$\-\u00A2-\uFFFF]/;
|
||||
|
||||
exports.retrievePrecedingIdentifier = function(text, pos, regex) {
|
||||
regex = regex || ID_REGEX;
|
||||
var buf = [];
|
||||
for (var i = pos-1; i >= 0; i--) {
|
||||
if (regex.test(text[i]))
|
||||
buf.push(text[i]);
|
||||
else
|
||||
break;
|
||||
}
|
||||
return buf.reverse().join("");
|
||||
};
|
||||
|
||||
exports.retrieveFollowingIdentifier = function(text, pos, regex) {
|
||||
regex = regex || ID_REGEX;
|
||||
var buf = [];
|
||||
for (var i = pos; i < text.length; i++) {
|
||||
if (regex.test(text[i]))
|
||||
buf.push(text[i]);
|
||||
else
|
||||
break;
|
||||
}
|
||||
return buf;
|
||||
};
|
||||
|
||||
exports.getCompletionPrefix = function (editor) {
|
||||
var pos = editor.getCursorPosition();
|
||||
var line = editor.session.getLine(pos.row);
|
||||
var prefix;
|
||||
editor.completers.forEach(function(completer) {
|
||||
if (completer.identifierRegexps) {
|
||||
completer.identifierRegexps.forEach(function(identifierRegex) {
|
||||
if (!prefix && identifierRegex)
|
||||
prefix = this.retrievePrecedingIdentifier(line, pos.column, identifierRegex);
|
||||
}.bind(this));
|
||||
}
|
||||
}.bind(this));
|
||||
return prefix || this.retrievePrecedingIdentifier(line, pos.column);
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,100 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
if (typeof process !== "undefined") {
|
||||
require("amd-loader");
|
||||
require("./test/mockdom");
|
||||
}
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var ace = require("./ace");
|
||||
var assert = require("./test/assertions");
|
||||
require("./ext/language_tools");
|
||||
|
||||
module.exports = {
|
||||
"test: highlighting in the popup" : function(done) {
|
||||
var editor = ace.edit(null, {
|
||||
value: "\narraysort alooooooooooooooooooooooooooooong_word",
|
||||
maxLines: 10,
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true
|
||||
});
|
||||
// editor.container.style.width = "500px";
|
||||
// editor.container.style.height = "500px";
|
||||
document.body.appendChild(editor.container);
|
||||
assert.ok(!editor.container.querySelector("style"));
|
||||
|
||||
// workaround for autocomplete using non-relative path
|
||||
editor.renderer.$themeId = "./theme/textmate";
|
||||
|
||||
editor.execCommand("insertstring", "a");
|
||||
checkInnerHTML('<d "ace_line ace_selected"><s "ace_completion-highlight">a</s><s "ace_">rraysort</s><s "ace_completion-meta">local</s></d><d "ace_line"><s "ace_completion-highlight">a</s><s "ace_">looooooooooooooooooooooooooooong_word</s><s "ace_completion-meta">local</s></d>', function() {
|
||||
editor.execCommand("insertstring", "rr");
|
||||
checkInnerHTML('<d "ace_line ace_selected"><s "ace_completion-highlight">arr</s><s "ace_">aysort</s><s "ace_completion-meta">local</s></d>', function() {
|
||||
editor.execCommand("insertstring", "r");
|
||||
checkInnerHTML('<d "ace_line ace_selected"><s "ace_completion-highlight">arr</s><s "ace_">ayso</s><s "ace_completion-highlight">r</s><s "ace_">t</s><s "ace_completion-meta">local</s></d>', function() {
|
||||
|
||||
editor.onCommandKey(null, 0, 13);
|
||||
assert.equal(editor.getValue(), "arraysort\narraysort alooooooooooooooooooooooooooooong_word");
|
||||
editor.execCommand("insertstring", " looooooooooooooooooooooooooooong_");
|
||||
checkInnerHTML('<d "ace_line ace_selected"><s "ace_">a</s><s "ace_completion-highlight">looooooooooooooooooooooooooooong_</s><s "ace_">word</s><s "ace_completion-meta">local</s></d>', function() {
|
||||
editor.onCommandKey(null, 0, 13);
|
||||
editor.destroy();
|
||||
editor.container.remove();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var last;
|
||||
function checkInnerHTML(expected, callback) {
|
||||
var popup = editor.completer.popup;
|
||||
|
||||
popup.renderer.on("afterRender", function wait() {
|
||||
var innerHTML = popup.renderer.$textLayer.element.innerHTML
|
||||
.replace(/\s*style="[^"]+"|class=|(d)iv|(s)pan/g, "$1$2");
|
||||
if (innerHTML == last)
|
||||
return;
|
||||
assert.equal(innerHTML, expected);
|
||||
last = innerHTML;
|
||||
popup.renderer.off("afterRender", wait);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if (typeof module !== "undefined" && module === require.main) {
|
||||
require("asyncjs").test.testcase(module.exports).exec();
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var oop = require("./lib/oop");
|
||||
var EventEmitter = require("./lib/event_emitter").EventEmitter;
|
||||
|
||||
|
||||
/**
|
||||
* Tokenizes the current [[Document `Document`]] in the background, and caches the tokenized rows for future use.
|
||||
*
|
||||
* If a certain row is changed, everything below that row is re-tokenized.
|
||||
*
|
||||
* @class BackgroundTokenizer
|
||||
**/
|
||||
|
||||
/**
|
||||
* Creates a new `BackgroundTokenizer` object.
|
||||
* @param {Tokenizer} tokenizer The tokenizer to use
|
||||
* @param {Editor} editor The editor to associate with
|
||||
*
|
||||
* @constructor
|
||||
**/
|
||||
|
||||
var BackgroundTokenizer = function(tokenizer, editor) {
|
||||
this.running = false;
|
||||
this.lines = [];
|
||||
this.states = [];
|
||||
this.currentLine = 0;
|
||||
this.tokenizer = tokenizer;
|
||||
|
||||
var self = this;
|
||||
|
||||
this.$worker = function() {
|
||||
if (!self.running) { return; }
|
||||
|
||||
var workerStart = new Date();
|
||||
var currentLine = self.currentLine;
|
||||
var endLine = -1;
|
||||
var doc = self.doc;
|
||||
|
||||
var startLine = currentLine;
|
||||
while (self.lines[currentLine])
|
||||
currentLine++;
|
||||
|
||||
var len = doc.getLength();
|
||||
var processedLines = 0;
|
||||
self.running = false;
|
||||
while (currentLine < len) {
|
||||
self.$tokenizeRow(currentLine);
|
||||
endLine = currentLine;
|
||||
do {
|
||||
currentLine++;
|
||||
} while (self.lines[currentLine]);
|
||||
|
||||
// only check every 5 lines
|
||||
processedLines ++;
|
||||
if ((processedLines % 5 === 0) && (new Date() - workerStart) > 20) {
|
||||
self.running = setTimeout(self.$worker, 20);
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.currentLine = currentLine;
|
||||
|
||||
if (endLine == -1)
|
||||
endLine = currentLine;
|
||||
|
||||
if (startLine <= endLine)
|
||||
self.fireUpdateEvent(startLine, endLine);
|
||||
};
|
||||
};
|
||||
|
||||
(function(){
|
||||
|
||||
oop.implement(this, EventEmitter);
|
||||
|
||||
/**
|
||||
* Sets a new tokenizer for this object.
|
||||
*
|
||||
* @param {Tokenizer} tokenizer The new tokenizer to use
|
||||
*
|
||||
**/
|
||||
this.setTokenizer = function(tokenizer) {
|
||||
this.tokenizer = tokenizer;
|
||||
this.lines = [];
|
||||
this.states = [];
|
||||
|
||||
this.start(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a new document to associate with this object.
|
||||
* @param {Document} doc The new document to associate with
|
||||
**/
|
||||
this.setDocument = function(doc) {
|
||||
this.doc = doc;
|
||||
this.lines = [];
|
||||
this.states = [];
|
||||
|
||||
this.stop();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fires whenever the background tokeniziers between a range of rows are going to be updated.
|
||||
*
|
||||
* @event update
|
||||
* @param {Object} e An object containing two properties, `first` and `last`, which indicate the rows of the region being updated.
|
||||
*
|
||||
**/
|
||||
/**
|
||||
* Emits the `'update'` event. `firstRow` and `lastRow` are used to define the boundaries of the region to be updated.
|
||||
* @param {Number} firstRow The starting row region
|
||||
* @param {Number} lastRow The final row region
|
||||
*
|
||||
**/
|
||||
this.fireUpdateEvent = function(firstRow, lastRow) {
|
||||
var data = {
|
||||
first: firstRow,
|
||||
last: lastRow
|
||||
};
|
||||
this._signal("update", {data: data});
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts tokenizing at the row indicated.
|
||||
*
|
||||
* @param {Number} startRow The row to start at
|
||||
*
|
||||
**/
|
||||
this.start = function(startRow) {
|
||||
this.currentLine = Math.min(startRow || 0, this.currentLine, this.doc.getLength());
|
||||
|
||||
// remove all cached items below this line
|
||||
this.lines.splice(this.currentLine, this.lines.length);
|
||||
this.states.splice(this.currentLine, this.states.length);
|
||||
|
||||
this.stop();
|
||||
// pretty long delay to prevent the tokenizer from interfering with the user
|
||||
this.running = setTimeout(this.$worker, 700);
|
||||
};
|
||||
|
||||
this.scheduleStart = function() {
|
||||
if (!this.running)
|
||||
this.running = setTimeout(this.$worker, 700);
|
||||
};
|
||||
|
||||
this.$updateOnChange = function(delta) {
|
||||
var startRow = delta.start.row;
|
||||
var len = delta.end.row - startRow;
|
||||
|
||||
if (len === 0) {
|
||||
this.lines[startRow] = null;
|
||||
} else if (delta.action == "remove") {
|
||||
this.lines.splice(startRow, len + 1, null);
|
||||
this.states.splice(startRow, len + 1, null);
|
||||
} else {
|
||||
var args = Array(len + 1);
|
||||
args.unshift(startRow, 1);
|
||||
this.lines.splice.apply(this.lines, args);
|
||||
this.states.splice.apply(this.states, args);
|
||||
}
|
||||
|
||||
this.currentLine = Math.min(startRow, this.currentLine, this.doc.getLength());
|
||||
|
||||
this.stop();
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops tokenizing.
|
||||
*
|
||||
**/
|
||||
this.stop = function() {
|
||||
if (this.running)
|
||||
clearTimeout(this.running);
|
||||
this.running = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gives list of tokens of the row. (tokens are cached)
|
||||
*
|
||||
* @param {Number} row The row to get tokens at
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
this.getTokens = function(row) {
|
||||
return this.lines[row] || this.$tokenizeRow(row);
|
||||
};
|
||||
|
||||
/**
|
||||
* [Returns the state of tokenization at the end of a row.]{: #BackgroundTokenizer.getState}
|
||||
*
|
||||
* @param {Number} row The row to get state at
|
||||
**/
|
||||
this.getState = function(row) {
|
||||
if (this.currentLine == row)
|
||||
this.$tokenizeRow(row);
|
||||
return this.states[row] || "start";
|
||||
};
|
||||
|
||||
this.$tokenizeRow = function(row) {
|
||||
var line = this.doc.getLine(row);
|
||||
var state = this.states[row - 1];
|
||||
|
||||
var data = this.tokenizer.getLineTokens(line, state, row);
|
||||
|
||||
if (this.states[row] + "" !== data.state + "") {
|
||||
this.states[row] = data.state;
|
||||
this.lines[row + 1] = null;
|
||||
if (this.currentLine > row + 1)
|
||||
this.currentLine = row + 1;
|
||||
} else if (this.currentLine == row) {
|
||||
this.currentLine = row + 1;
|
||||
}
|
||||
|
||||
return this.lines[row] = data.tokens;
|
||||
};
|
||||
|
||||
}).call(BackgroundTokenizer.prototype);
|
||||
|
||||
exports.BackgroundTokenizer = BackgroundTokenizer;
|
||||
});
|
||||
@@ -1,152 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
if (typeof process !== "undefined") {
|
||||
require("amd-loader");
|
||||
}
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var EditSession = require("./edit_session").EditSession;
|
||||
var JavaScriptMode = require("./mode/javascript").Mode;
|
||||
var LuaMode = require("./mode/lua").Mode;
|
||||
var Range = require("./range").Range;
|
||||
var assert = require("./test/assertions");
|
||||
|
||||
function forceTokenize(session, startLine) {
|
||||
for (var i = startLine || 0, l = session.getLength(); i < l; i++)
|
||||
session.getTokens(i);
|
||||
}
|
||||
|
||||
function testStates(session, states) {
|
||||
for (var i = 0, l = session.getLength(); i < l; i++)
|
||||
assert.equal(session.bgTokenizer.states[i], states[i]);
|
||||
assert.ok(l == states.length);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
"test background tokenizer update on session change" : function() {
|
||||
var doc = new EditSession([
|
||||
"/*",
|
||||
"*/",
|
||||
"var juhu"
|
||||
]);
|
||||
doc.setMode("./mode/javascript");
|
||||
|
||||
forceTokenize(doc);
|
||||
testStates(doc, ["comment1", "start", "no_regex"]);
|
||||
|
||||
doc.remove(new Range(0,2,1,2));
|
||||
testStates(doc, [null, "no_regex"]);
|
||||
|
||||
forceTokenize(doc);
|
||||
testStates(doc, ["comment1", "comment1"]);
|
||||
|
||||
doc.insert({row:0, column:2}, "\n*/");
|
||||
testStates(doc, [undefined, undefined, "comment1"]);
|
||||
|
||||
forceTokenize(doc);
|
||||
testStates(doc, ["comment1", "start", "no_regex"]);
|
||||
},
|
||||
"test background tokenizer sends update event" : function() {
|
||||
var doc = new EditSession([
|
||||
"/*",
|
||||
"var",
|
||||
"juhu",
|
||||
"*/"
|
||||
]);
|
||||
doc.setMode("./mode/javascript");
|
||||
|
||||
var updateEvent = null;
|
||||
doc.bgTokenizer.on("update", function(e) {
|
||||
updateEvent = e.data;
|
||||
});
|
||||
function checkEvent(first, last) {
|
||||
assert.ok(!updateEvent, "unneccessary update event");
|
||||
doc.bgTokenizer.running = 1;
|
||||
doc.bgTokenizer.$worker();
|
||||
assert.ok(updateEvent);
|
||||
assert.equal([first, last] + "",
|
||||
[updateEvent.first, updateEvent.last] + "");
|
||||
updateEvent = null;
|
||||
}
|
||||
|
||||
forceTokenize(doc);
|
||||
var comment = "comment1";
|
||||
testStates(doc, [comment, comment, comment, "start"]);
|
||||
|
||||
doc.remove(new Range(0,0,0,2));
|
||||
testStates(doc, [comment, comment, comment, "start"]);
|
||||
|
||||
checkEvent(0, 3);
|
||||
testStates(doc, ["start", "no_regex", "no_regex", "regex"]);
|
||||
|
||||
// insert /* and and press down several times quickly
|
||||
doc.insert({row:0, column:0}, "/*");
|
||||
doc.getTokens(0);
|
||||
doc.getTokens(1);
|
||||
doc.getTokens(2);
|
||||
checkEvent(0, 3);
|
||||
|
||||
forceTokenize(doc);
|
||||
testStates(doc, [comment, comment, comment, "start"]);
|
||||
},
|
||||
"test background tokenizer sends update event 2" : function(next) {
|
||||
var doc = new EditSession([
|
||||
"-[[",
|
||||
"juhu",
|
||||
"kinners]]--",
|
||||
""
|
||||
]);
|
||||
doc.setMode("./mode/lua");
|
||||
forceTokenize(doc);
|
||||
var string = "bracketedString,2,start";
|
||||
var comment = "bracketedComment,2,start";
|
||||
testStates(doc, [string, string, "start", "start"]);
|
||||
|
||||
doc.insert({row:0, column:0}, "-");
|
||||
forceTokenize(doc);
|
||||
doc.bgTokenizer.once("update", function(e) {
|
||||
assert.equal([0, 4] + "", [e.data.first, e.data.last] + "");
|
||||
testStates(doc, [comment, comment, "start", "start"]);
|
||||
next();
|
||||
});
|
||||
doc.bgTokenizer.running = 1;
|
||||
doc.bgTokenizer.$worker();
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
if (typeof module !== "undefined" && module === require.main) {
|
||||
require("asyncjs").test.testcase(module.exports).exec();
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var bidiUtil = require("./lib/bidiutil");
|
||||
var lang = require("./lib/lang");
|
||||
var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac\u202B]/;
|
||||
|
||||
/**
|
||||
* This object is used to ensure Bi-Directional support (for languages with text flowing from right to left, like Arabic or Hebrew)
|
||||
* including correct caret positioning, text selection mouse and keyboard arrows functioning
|
||||
* @class BidiHandler
|
||||
**/
|
||||
|
||||
/**
|
||||
* Creates a new `BidiHandler` object
|
||||
* @param {EditSession} session The session to use
|
||||
*
|
||||
* @constructor
|
||||
**/
|
||||
var BidiHandler = function(session) {
|
||||
this.session = session;
|
||||
this.bidiMap = {};
|
||||
/* current screen row */
|
||||
this.currentRow = null;
|
||||
this.bidiUtil = bidiUtil;
|
||||
/* Arabic/Hebrew character width differs from regular character width */
|
||||
this.charWidths = [];
|
||||
this.EOL = "\xAC";
|
||||
this.showInvisibles = true;
|
||||
this.isRtlDir = false;
|
||||
this.$isRtl = false;
|
||||
this.line = "";
|
||||
this.wrapIndent = 0;
|
||||
this.EOF = "\xB6";
|
||||
this.RLE = "\u202B";
|
||||
this.contentWidth = 0;
|
||||
this.fontMetrics = null;
|
||||
this.rtlLineOffset = 0;
|
||||
this.wrapOffset = 0;
|
||||
this.isMoveLeftOperation = false;
|
||||
this.seenBidi = bidiRE.test(session.getValue());
|
||||
};
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* Returns 'true' if row contains Bidi characters, in such case
|
||||
* creates Bidi map to be used in operations related to selection
|
||||
* (keyboard arrays, mouse click, select)
|
||||
* @param {Number} the screen row to be checked
|
||||
* @param {Number} the document row to be checked [optional]
|
||||
* @param {Number} the wrapped screen line index [ optional]
|
||||
**/
|
||||
this.isBidiRow = function(screenRow, docRow, splitIndex) {
|
||||
if (!this.seenBidi)
|
||||
return false;
|
||||
if (screenRow !== this.currentRow) {
|
||||
this.currentRow = screenRow;
|
||||
this.updateRowLine(docRow, splitIndex);
|
||||
this.updateBidiMap();
|
||||
}
|
||||
return this.bidiMap.bidiLevels;
|
||||
};
|
||||
|
||||
this.onChange = function(delta) {
|
||||
if (!this.seenBidi) {
|
||||
if (delta.action == "insert" && bidiRE.test(delta.lines.join("\n"))) {
|
||||
this.seenBidi = true;
|
||||
this.currentRow = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.currentRow = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.getDocumentRow = function() {
|
||||
var docRow = 0;
|
||||
var rowCache = this.session.$screenRowCache;
|
||||
if (rowCache.length) {
|
||||
var index = this.session.$getRowCacheIndex(rowCache, this.currentRow);
|
||||
if (index >= 0)
|
||||
docRow = this.session.$docRowCache[index];
|
||||
}
|
||||
|
||||
return docRow;
|
||||
};
|
||||
|
||||
this.getSplitIndex = function() {
|
||||
var splitIndex = 0;
|
||||
var rowCache = this.session.$screenRowCache;
|
||||
if (rowCache.length) {
|
||||
var currentIndex, prevIndex = this.session.$getRowCacheIndex(rowCache, this.currentRow);
|
||||
while (this.currentRow - splitIndex > 0) {
|
||||
currentIndex = this.session.$getRowCacheIndex(rowCache, this.currentRow - splitIndex - 1);
|
||||
if (currentIndex !== prevIndex)
|
||||
break;
|
||||
|
||||
prevIndex = currentIndex;
|
||||
splitIndex++;
|
||||
}
|
||||
} else {
|
||||
splitIndex = this.currentRow;
|
||||
}
|
||||
|
||||
return splitIndex;
|
||||
};
|
||||
|
||||
this.updateRowLine = function(docRow, splitIndex) {
|
||||
if (docRow === undefined)
|
||||
docRow = this.getDocumentRow();
|
||||
|
||||
var isLastRow = (docRow === this.session.getLength() - 1),
|
||||
endOfLine = isLastRow ? this.EOF : this.EOL;
|
||||
|
||||
this.wrapIndent = 0;
|
||||
this.line = this.session.getLine(docRow);
|
||||
this.isRtlDir = this.$isRtl || this.line.charAt(0) === this.RLE;
|
||||
if (this.session.$useWrapMode) {
|
||||
var splits = this.session.$wrapData[docRow];
|
||||
if (splits) {
|
||||
if (splitIndex === undefined)
|
||||
splitIndex = this.getSplitIndex();
|
||||
|
||||
if(splitIndex > 0 && splits.length) {
|
||||
this.wrapIndent = splits.indent;
|
||||
this.wrapOffset = this.wrapIndent * this.charWidths[bidiUtil.L];
|
||||
this.line = (splitIndex < splits.length) ?
|
||||
this.line.substring(splits[splitIndex - 1], splits[splitIndex]) :
|
||||
this.line.substring(splits[splits.length - 1]);
|
||||
} else {
|
||||
this.line = this.line.substring(0, splits[splitIndex]);
|
||||
}
|
||||
}
|
||||
if (splitIndex == splits.length)
|
||||
this.line += (this.showInvisibles) ? endOfLine : bidiUtil.DOT;
|
||||
} else {
|
||||
this.line += this.showInvisibles ? endOfLine : bidiUtil.DOT;
|
||||
}
|
||||
|
||||
/* replace tab and wide characters by commensurate spaces */
|
||||
var session = this.session, shift = 0, size;
|
||||
this.line = this.line.replace(/\t|[\u1100-\u2029, \u202F-\uFFE6]/g, function(ch, i){
|
||||
if (ch === '\t' || session.isFullWidth(ch.charCodeAt(0))) {
|
||||
size = (ch === '\t') ? session.getScreenTabSize(i + shift) : 2;
|
||||
shift += size - 1;
|
||||
return lang.stringRepeat(bidiUtil.DOT, size);
|
||||
}
|
||||
return ch;
|
||||
});
|
||||
|
||||
if (this.isRtlDir) {
|
||||
this.fontMetrics.$main.textContent = (this.line.charAt(this.line.length - 1) == bidiUtil.DOT) ? this.line.substr(0, this.line.length - 1) : this.line;
|
||||
this.rtlLineOffset = this.contentWidth - this.fontMetrics.$main.getBoundingClientRect().width;
|
||||
}
|
||||
};
|
||||
|
||||
this.updateBidiMap = function() {
|
||||
var textCharTypes = [];
|
||||
if (bidiUtil.hasBidiCharacters(this.line, textCharTypes) || this.isRtlDir) {
|
||||
this.bidiMap = bidiUtil.doBidiReorder(this.line, textCharTypes, this.isRtlDir);
|
||||
} else {
|
||||
this.bidiMap = {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets stored info related to current screen row
|
||||
**/
|
||||
this.markAsDirty = function() {
|
||||
this.currentRow = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates array of character widths
|
||||
* @param {Object} font metrics
|
||||
*
|
||||
**/
|
||||
this.updateCharacterWidths = function(fontMetrics) {
|
||||
if (this.characterWidth === fontMetrics.$characterSize.width)
|
||||
return;
|
||||
|
||||
this.fontMetrics = fontMetrics;
|
||||
var characterWidth = this.characterWidth = fontMetrics.$characterSize.width;
|
||||
var bidiCharWidth = fontMetrics.$measureCharWidth("\u05d4");
|
||||
|
||||
this.charWidths[bidiUtil.L] = this.charWidths[bidiUtil.EN] = this.charWidths[bidiUtil.ON_R] = characterWidth;
|
||||
this.charWidths[bidiUtil.R] = this.charWidths[bidiUtil.AN] = bidiCharWidth;
|
||||
this.charWidths[bidiUtil.R_H] = bidiCharWidth * 0.45;
|
||||
this.charWidths[bidiUtil.B] = this.charWidths[bidiUtil.RLE] = 0;
|
||||
|
||||
this.currentRow = null;
|
||||
};
|
||||
|
||||
this.setShowInvisibles = function(showInvisibles) {
|
||||
this.showInvisibles = showInvisibles;
|
||||
this.currentRow = null;
|
||||
};
|
||||
|
||||
this.setEolChar = function(eolChar) {
|
||||
this.EOL = eolChar;
|
||||
};
|
||||
|
||||
this.setContentWidth = function(width) {
|
||||
this.contentWidth = width;
|
||||
};
|
||||
|
||||
this.isRtlLine = function(row) {
|
||||
if (this.$isRtl) return true;
|
||||
if (row != undefined)
|
||||
return (this.session.getLine(row).charAt(0) == this.RLE);
|
||||
else
|
||||
return this.isRtlDir;
|
||||
};
|
||||
|
||||
this.setRtlDirection = function(editor, isRtlDir) {
|
||||
var cursor = editor.getCursorPosition();
|
||||
for (var row = editor.selection.getSelectionAnchor().row; row <= cursor.row; row++) {
|
||||
if (!isRtlDir && editor.session.getLine(row).charAt(0) === editor.session.$bidiHandler.RLE)
|
||||
editor.session.doc.removeInLine(row, 0, 1);
|
||||
else if (isRtlDir && editor.session.getLine(row).charAt(0) !== editor.session.$bidiHandler.RLE)
|
||||
editor.session.doc.insert({column: 0, row: row}, editor.session.$bidiHandler.RLE);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns offset of character at position defined by column.
|
||||
* @param {Number} the screen column position
|
||||
*
|
||||
* @return {int} horizontal pixel offset of given screen column
|
||||
**/
|
||||
this.getPosLeft = function(col) {
|
||||
col -= this.wrapIndent;
|
||||
var leftBoundary = (this.line.charAt(0) === this.RLE) ? 1 : 0;
|
||||
var logicalIdx = (col > leftBoundary) ? (this.session.getOverwrite() ? col : col - 1) : leftBoundary;
|
||||
var visualIdx = bidiUtil.getVisualFromLogicalIdx(logicalIdx, this.bidiMap),
|
||||
levels = this.bidiMap.bidiLevels, left = 0;
|
||||
|
||||
if (!this.session.getOverwrite() && col <= leftBoundary && levels[visualIdx] % 2 !== 0)
|
||||
visualIdx++;
|
||||
|
||||
for (var i = 0; i < visualIdx; i++) {
|
||||
left += this.charWidths[levels[i]];
|
||||
}
|
||||
|
||||
if (!this.session.getOverwrite() && (col > leftBoundary) && (levels[visualIdx] % 2 === 0))
|
||||
left += this.charWidths[levels[visualIdx]];
|
||||
|
||||
if (this.wrapIndent)
|
||||
left += this.isRtlDir ? (-1 * this.wrapOffset) : this.wrapOffset;
|
||||
|
||||
if (this.isRtlDir)
|
||||
left += this.rtlLineOffset;
|
||||
|
||||
return left;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns 'selections' - array of objects defining set of selection rectangles
|
||||
* @param {Number} the start column position
|
||||
* @param {Number} the end column position
|
||||
*
|
||||
* @return {Array of Objects} Each object contains 'left' and 'width' values defining selection rectangle.
|
||||
**/
|
||||
this.getSelections = function(startCol, endCol) {
|
||||
var map = this.bidiMap, levels = map.bidiLevels, level, selections = [], offset = 0,
|
||||
selColMin = Math.min(startCol, endCol) - this.wrapIndent, selColMax = Math.max(startCol, endCol) - this.wrapIndent,
|
||||
isSelected = false, isSelectedPrev = false, selectionStart = 0;
|
||||
|
||||
if (this.wrapIndent)
|
||||
offset += this.isRtlDir ? (-1 * this.wrapOffset) : this.wrapOffset;
|
||||
|
||||
for (var logIdx, visIdx = 0; visIdx < levels.length; visIdx++) {
|
||||
logIdx = map.logicalFromVisual[visIdx];
|
||||
level = levels[visIdx];
|
||||
isSelected = (logIdx >= selColMin) && (logIdx < selColMax);
|
||||
if (isSelected && !isSelectedPrev) {
|
||||
selectionStart = offset;
|
||||
} else if (!isSelected && isSelectedPrev) {
|
||||
selections.push({left: selectionStart, width: offset - selectionStart});
|
||||
}
|
||||
offset += this.charWidths[level];
|
||||
isSelectedPrev = isSelected;
|
||||
}
|
||||
|
||||
if (isSelected && (visIdx === levels.length)) {
|
||||
selections.push({left: selectionStart, width: offset - selectionStart});
|
||||
}
|
||||
|
||||
if(this.isRtlDir) {
|
||||
for (var i = 0; i < selections.length; i++) {
|
||||
selections[i].left += this.rtlLineOffset;
|
||||
}
|
||||
}
|
||||
return selections;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts character coordinates on the screen to respective document column number
|
||||
* @param {int} character horizontal offset
|
||||
*
|
||||
* @return {Number} screen column number corresponding to given pixel offset
|
||||
**/
|
||||
this.offsetToCol = function(posX) {
|
||||
if(this.isRtlDir)
|
||||
posX -= this.rtlLineOffset;
|
||||
|
||||
var logicalIdx = 0, posX = Math.max(posX, 0),
|
||||
offset = 0, visualIdx = 0, levels = this.bidiMap.bidiLevels,
|
||||
charWidth = this.charWidths[levels[visualIdx]];
|
||||
|
||||
if (this.wrapIndent)
|
||||
posX -= this.isRtlDir ? (-1 * this.wrapOffset) : this.wrapOffset;
|
||||
|
||||
while(posX > offset + charWidth/2) {
|
||||
offset += charWidth;
|
||||
if(visualIdx === levels.length - 1) {
|
||||
/* quit when we on the right of the last character, flag this by charWidth = 0 */
|
||||
charWidth = 0;
|
||||
break;
|
||||
}
|
||||
charWidth = this.charWidths[levels[++visualIdx]];
|
||||
}
|
||||
|
||||
if (visualIdx > 0 && (levels[visualIdx - 1] % 2 !== 0) && (levels[visualIdx] % 2 === 0)){
|
||||
/* Bidi character on the left and None Bidi character on the right */
|
||||
if(posX < offset)
|
||||
visualIdx--;
|
||||
logicalIdx = this.bidiMap.logicalFromVisual[visualIdx];
|
||||
|
||||
} else if (visualIdx > 0 && (levels[visualIdx - 1] % 2 === 0) && (levels[visualIdx] % 2 !== 0)){
|
||||
/* None Bidi character on the left and Bidi character on the right */
|
||||
logicalIdx = 1 + ((posX > offset) ? this.bidiMap.logicalFromVisual[visualIdx]
|
||||
: this.bidiMap.logicalFromVisual[visualIdx - 1]);
|
||||
|
||||
} else if ((this.isRtlDir && visualIdx === levels.length - 1 && charWidth === 0 && (levels[visualIdx - 1] % 2 === 0))
|
||||
|| (!this.isRtlDir && visualIdx === 0 && (levels[visualIdx] % 2 !== 0))){
|
||||
/* To the right of last character, which is None Bidi, in RTL direction or */
|
||||
/* to the left of first Bidi character, in LTR direction */
|
||||
logicalIdx = 1 + this.bidiMap.logicalFromVisual[visualIdx];
|
||||
} else {
|
||||
/* Tweak visual position when Bidi character on the left in order to map it to corresponding logical position */
|
||||
if (visualIdx > 0 && (levels[visualIdx - 1] % 2 !== 0) && charWidth !== 0)
|
||||
visualIdx--;
|
||||
|
||||
/* Regular case */
|
||||
logicalIdx = this.bidiMap.logicalFromVisual[visualIdx];
|
||||
}
|
||||
|
||||
if (logicalIdx === 0 && this.isRtlDir)
|
||||
logicalIdx++;
|
||||
|
||||
return (logicalIdx + this.wrapIndent);
|
||||
};
|
||||
|
||||
}).call(BidiHandler.prototype);
|
||||
|
||||
exports.BidiHandler = BidiHandler;
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2012, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var $cancelT;
|
||||
module.exports = {
|
||||
lineMode: false,
|
||||
pasteCancelled: function() {
|
||||
if ($cancelT && $cancelT > Date.now() - 50)
|
||||
return true;
|
||||
return $cancelT = false;
|
||||
},
|
||||
cancel: function() {
|
||||
$cancelT = Date.now();
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,121 +0,0 @@
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var oop = require("../lib/oop");
|
||||
var MultiHashHandler = require("../keyboard/hash_handler").MultiHashHandler;
|
||||
var EventEmitter = require("../lib/event_emitter").EventEmitter;
|
||||
|
||||
/**
|
||||
* @class CommandManager
|
||||
*
|
||||
**/
|
||||
|
||||
/**
|
||||
* new CommandManager(platform, commands)
|
||||
* @param {String} platform Identifier for the platform; must be either `"mac"` or `"win"`
|
||||
* @param {Array} commands A list of commands
|
||||
*
|
||||
**/
|
||||
|
||||
var CommandManager = function(platform, commands) {
|
||||
MultiHashHandler.call(this, commands, platform);
|
||||
this.byName = this.commands;
|
||||
this.setDefaultHandler("exec", function(e) {
|
||||
return e.command.exec(e.editor, e.args || {});
|
||||
});
|
||||
};
|
||||
|
||||
oop.inherits(CommandManager, MultiHashHandler);
|
||||
|
||||
(function() {
|
||||
|
||||
oop.implement(this, EventEmitter);
|
||||
|
||||
this.exec = function(command, editor, args) {
|
||||
if (Array.isArray(command)) {
|
||||
for (var i = command.length; i--; ) {
|
||||
if (this.exec(command[i], editor, args)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof command === "string")
|
||||
command = this.commands[command];
|
||||
|
||||
if (!command)
|
||||
return false;
|
||||
|
||||
if (editor && editor.$readOnly && !command.readOnly)
|
||||
return false;
|
||||
|
||||
if (this.$checkCommandState != false && command.isAvailable && !command.isAvailable(editor))
|
||||
return false;
|
||||
|
||||
var e = {editor: editor, command: command, args: args};
|
||||
e.returnValue = this._emit("exec", e);
|
||||
this._signal("afterExec", e);
|
||||
|
||||
return e.returnValue === false ? false : true;
|
||||
};
|
||||
|
||||
this.toggleRecording = function(editor) {
|
||||
if (this.$inReplay)
|
||||
return;
|
||||
|
||||
editor && editor._emit("changeStatus");
|
||||
if (this.recording) {
|
||||
this.macro.pop();
|
||||
this.removeEventListener("exec", this.$addCommandToMacro);
|
||||
|
||||
if (!this.macro.length)
|
||||
this.macro = this.oldMacro;
|
||||
|
||||
return this.recording = false;
|
||||
}
|
||||
if (!this.$addCommandToMacro) {
|
||||
this.$addCommandToMacro = function(e) {
|
||||
this.macro.push([e.command, e.args]);
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
this.oldMacro = this.macro;
|
||||
this.macro = [];
|
||||
this.on("exec", this.$addCommandToMacro);
|
||||
return this.recording = true;
|
||||
};
|
||||
|
||||
this.replay = function(editor) {
|
||||
if (this.$inReplay || !this.macro)
|
||||
return;
|
||||
|
||||
if (this.recording)
|
||||
return this.toggleRecording(editor);
|
||||
|
||||
try {
|
||||
this.$inReplay = true;
|
||||
this.macro.forEach(function(x) {
|
||||
if (typeof x == "string")
|
||||
this.exec(x, editor);
|
||||
else
|
||||
this.exec(x[0], editor, x[1]);
|
||||
}, this);
|
||||
} finally {
|
||||
this.$inReplay = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.trimMacro = function(m) {
|
||||
return m.map(function(x){
|
||||
if (typeof x[0] != "string")
|
||||
x[0] = x[0].name;
|
||||
if (!x[1])
|
||||
x = x[0];
|
||||
return x;
|
||||
});
|
||||
};
|
||||
|
||||
}).call(CommandManager.prototype);
|
||||
|
||||
exports.CommandManager = CommandManager;
|
||||
|
||||
});
|
||||
@@ -1,211 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
if (typeof process !== "undefined") {
|
||||
require("amd-loader");
|
||||
}
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var CommandManager = require("./command_manager").CommandManager;
|
||||
var keys = require("../lib/keys");
|
||||
var assert = require("../test/assertions");
|
||||
|
||||
module.exports = {
|
||||
|
||||
setUp: function() {
|
||||
this.command = {
|
||||
name: "gotoline",
|
||||
bindKey: {
|
||||
mac: "Command-L",
|
||||
win: "Ctrl-L"
|
||||
},
|
||||
called: false,
|
||||
available: true,
|
||||
exec: function(editor) { this.called = true; },
|
||||
isAvailable: function(editor) { return this.available; }
|
||||
};
|
||||
|
||||
this.cm = new CommandManager("mac", [this.command]);
|
||||
},
|
||||
|
||||
"test: register command": function() {
|
||||
this.cm.exec("gotoline");
|
||||
assert.ok(this.command.called);
|
||||
},
|
||||
|
||||
"test: mac hotkeys": function() {
|
||||
var command = this.cm.findKeyCommand(keys.KEY_MODS.command, "l");
|
||||
assert.equal(command, this.command);
|
||||
|
||||
var command = this.cm.findKeyCommand(keys.KEY_MODS.ctrl, "l");
|
||||
assert.equal(command, undefined);
|
||||
},
|
||||
|
||||
"test: win hotkeys": function() {
|
||||
var cm = new CommandManager("win", [this.command]);
|
||||
|
||||
var command = cm.findKeyCommand(keys.KEY_MODS.command, "l");
|
||||
assert.equal(command, undefined);
|
||||
|
||||
var command = cm.findKeyCommand(keys.KEY_MODS.ctrl, "l");
|
||||
assert.equal(command, this.command);
|
||||
},
|
||||
|
||||
"test: command isAvailable": function() {
|
||||
this.command.available = false;
|
||||
|
||||
this.cm.exec("gotoline");
|
||||
assert.ok(!this.command.called);
|
||||
this.cm.$checkCommandState = false;
|
||||
this.cm.exec("gotoline");
|
||||
assert.ok(this.command.called);
|
||||
},
|
||||
|
||||
"test: remove command by object": function() {
|
||||
this.cm.removeCommand(this.command);
|
||||
|
||||
this.cm.exec("gotoline");
|
||||
assert.ok(!this.command.called);
|
||||
|
||||
var command = this.cm.findKeyCommand(keys.KEY_MODS.command, "l");
|
||||
assert.equal(command, null);
|
||||
},
|
||||
|
||||
"test: remove command by name": function() {
|
||||
this.cm.removeCommand("gotoline");
|
||||
|
||||
this.cm.exec("gotoline");
|
||||
assert.ok(!this.command.called);
|
||||
|
||||
var command = this.cm.findKeyCommand(keys.KEY_MODS.command, "l");
|
||||
assert.equal(command, null);
|
||||
},
|
||||
|
||||
"test: adding a new command with the same name as an existing one should remove the old one first": function() {
|
||||
var command = {
|
||||
name: "gotoline",
|
||||
bindKey: {
|
||||
mac: "Command-L",
|
||||
win: "Ctrl-L"
|
||||
},
|
||||
called: false,
|
||||
exec: function(editor) { this.called = true; }
|
||||
};
|
||||
this.cm.addCommand(command);
|
||||
|
||||
this.cm.exec("gotoline");
|
||||
assert.ok(command.called);
|
||||
assert.ok(!this.command.called);
|
||||
|
||||
assert.equal(this.cm.findKeyCommand(keys.KEY_MODS.command, "l"), command);
|
||||
},
|
||||
|
||||
"test: adding commands and recording a macro": function() {
|
||||
var called = "";
|
||||
this.cm.addCommands({
|
||||
togglerecording: function(editor) {
|
||||
editor.cm.toggleRecording(editor);
|
||||
},
|
||||
replay: function(editor) {
|
||||
editor.cm.replay();
|
||||
},
|
||||
cm1: function(editor, arg) {
|
||||
called += "1" + (arg || "");
|
||||
},
|
||||
cm2: function(editor) {
|
||||
called += "2";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var statusUpdateEmitted = false;
|
||||
this._emit = function() {statusUpdateEmitted = true;};
|
||||
|
||||
this.cm.exec("togglerecording", this);
|
||||
assert.ok(this.cm.recording);
|
||||
assert.ok(statusUpdateEmitted);
|
||||
|
||||
this.cm.exec("cm1", this, "-");
|
||||
this.cm.exec("cm2");
|
||||
this.cm.exec("replay", this);
|
||||
assert.ok(!this.cm.recording);
|
||||
assert.equal(called, "1-2");
|
||||
|
||||
called = "";
|
||||
this.cm.exec("replay", this);
|
||||
assert.equal(called, "1-2");
|
||||
},
|
||||
|
||||
"test: bindkeys": function() {
|
||||
this.cm.bindKeys({
|
||||
"Ctrl-L|Command-C": "cm1",
|
||||
"Ctrl-R": "cm2"
|
||||
});
|
||||
|
||||
var command = this.cm.findKeyCommand(keys.KEY_MODS.command, "c");
|
||||
assert.equal(command, "cm1");
|
||||
|
||||
var command = this.cm.findKeyCommand(keys.KEY_MODS.ctrl, "r");
|
||||
assert.equal(command, "cm2");
|
||||
|
||||
this.cm.bindKeys({
|
||||
"Ctrl-R": null
|
||||
});
|
||||
|
||||
var command = this.cm.findKeyCommand(keys.KEY_MODS.ctrl, "r");
|
||||
assert.equal(command, null);
|
||||
},
|
||||
|
||||
"test: binding keys without modifiers": function() {
|
||||
this.cm.bindKeys({
|
||||
"R": "cm1",
|
||||
"Shift-r": "cm2",
|
||||
"Return": "cm4",
|
||||
"Enter": "cm3"
|
||||
});
|
||||
|
||||
var command = this.cm.findKeyCommand(-1, "r");
|
||||
assert.equal(command, "cm1");
|
||||
|
||||
var command = this.cm.findKeyCommand(-1, "R");
|
||||
assert.equal(command, "cm2");
|
||||
|
||||
var command = this.cm.findKeyCommand(0, "return");
|
||||
assert.equal(command + "", ["cm4", "cm3"] + "");
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
if (typeof module !== "undefined" && module === require.main) {
|
||||
require("asyncjs").test.testcase(module.exports).exec();
|
||||
}
|
||||
@@ -1,860 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var lang = require("../lib/lang");
|
||||
var config = require("../config");
|
||||
var Range = require("../range").Range;
|
||||
|
||||
function bindKey(win, mac) {
|
||||
return {win: win, mac: mac};
|
||||
}
|
||||
|
||||
/*
|
||||
multiSelectAction: "forEach"|"forEachLine"|function|undefined,
|
||||
scrollIntoView: true|"cursor"|"center"|"selectionPart"
|
||||
*/
|
||||
exports.commands = [{
|
||||
name: "showSettingsMenu",
|
||||
bindKey: bindKey("Ctrl-,", "Command-,"),
|
||||
exec: function(editor) {
|
||||
config.loadModule("ace/ext/settings_menu", function(module) {
|
||||
module.init(editor);
|
||||
editor.showSettingsMenu();
|
||||
});
|
||||
},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "goToNextError",
|
||||
bindKey: bindKey("Alt-E", "F4"),
|
||||
exec: function(editor) {
|
||||
config.loadModule("./ext/error_marker", function(module) {
|
||||
module.showErrorMarker(editor, 1);
|
||||
});
|
||||
},
|
||||
scrollIntoView: "animate",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "goToPreviousError",
|
||||
bindKey: bindKey("Alt-Shift-E", "Shift-F4"),
|
||||
exec: function(editor) {
|
||||
config.loadModule("./ext/error_marker", function(module) {
|
||||
module.showErrorMarker(editor, -1);
|
||||
});
|
||||
},
|
||||
scrollIntoView: "animate",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectall",
|
||||
description: "Select all",
|
||||
bindKey: bindKey("Ctrl-A", "Command-A"),
|
||||
exec: function(editor) { editor.selectAll(); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "centerselection",
|
||||
description: "Center selection",
|
||||
bindKey: bindKey(null, "Ctrl-L"),
|
||||
exec: function(editor) { editor.centerSelection(); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "gotoline",
|
||||
description: "Go to line...",
|
||||
bindKey: bindKey("Ctrl-L", "Command-L"),
|
||||
exec: function(editor, line) {
|
||||
// backwards compatibility
|
||||
if (typeof line === "number" && !isNaN(line))
|
||||
editor.gotoLine(line);
|
||||
editor.prompt({ $type: "gotoLine" });
|
||||
},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "fold",
|
||||
bindKey: bindKey("Alt-L|Ctrl-F1", "Command-Alt-L|Command-F1"),
|
||||
exec: function(editor) { editor.session.toggleFold(false); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "center",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "unfold",
|
||||
bindKey: bindKey("Alt-Shift-L|Ctrl-Shift-F1", "Command-Alt-Shift-L|Command-Shift-F1"),
|
||||
exec: function(editor) { editor.session.toggleFold(true); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "center",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "toggleFoldWidget",
|
||||
bindKey: bindKey("F2", "F2"),
|
||||
exec: function(editor) { editor.session.toggleFoldWidget(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "center",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "toggleParentFoldWidget",
|
||||
bindKey: bindKey("Alt-F2", "Alt-F2"),
|
||||
exec: function(editor) { editor.session.toggleFoldWidget(true); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "center",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "foldall",
|
||||
description: "Fold all",
|
||||
bindKey: bindKey(null, "Ctrl-Command-Option-0"),
|
||||
exec: function(editor) { editor.session.foldAll(); },
|
||||
scrollIntoView: "center",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "foldOther",
|
||||
description: "Fold other",
|
||||
bindKey: bindKey("Alt-0", "Command-Option-0"),
|
||||
exec: function(editor) {
|
||||
editor.session.foldAll();
|
||||
editor.session.unfold(editor.selection.getAllRanges());
|
||||
},
|
||||
scrollIntoView: "center",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "unfoldall",
|
||||
description: "Unfold all",
|
||||
bindKey: bindKey("Alt-Shift-0", "Command-Option-Shift-0"),
|
||||
exec: function(editor) { editor.session.unfold(); },
|
||||
scrollIntoView: "center",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "findnext",
|
||||
description: "Find next",
|
||||
bindKey: bindKey("Ctrl-K", "Command-G"),
|
||||
exec: function(editor) { editor.findNext(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "center",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "findprevious",
|
||||
description: "Find previous",
|
||||
bindKey: bindKey("Ctrl-Shift-K", "Command-Shift-G"),
|
||||
exec: function(editor) { editor.findPrevious(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "center",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectOrFindNext",
|
||||
description: "Select or find next",
|
||||
bindKey: bindKey("Alt-K", "Ctrl-G"),
|
||||
exec: function(editor) {
|
||||
if (editor.selection.isEmpty())
|
||||
editor.selection.selectWord();
|
||||
else
|
||||
editor.findNext();
|
||||
},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectOrFindPrevious",
|
||||
description: "Select or find previous",
|
||||
bindKey: bindKey("Alt-Shift-K", "Ctrl-Shift-G"),
|
||||
exec: function(editor) {
|
||||
if (editor.selection.isEmpty())
|
||||
editor.selection.selectWord();
|
||||
else
|
||||
editor.findPrevious();
|
||||
},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "find",
|
||||
description: "Find",
|
||||
bindKey: bindKey("Ctrl-F", "Command-F"),
|
||||
exec: function(editor) {
|
||||
config.loadModule("ace/ext/searchbox", function(e) {e.Search(editor);});
|
||||
},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "overwrite",
|
||||
description: "Overwrite",
|
||||
bindKey: "Insert",
|
||||
exec: function(editor) { editor.toggleOverwrite(); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selecttostart",
|
||||
description: "Select to start",
|
||||
bindKey: bindKey("Ctrl-Shift-Home", "Command-Shift-Home|Command-Shift-Up"),
|
||||
exec: function(editor) { editor.getSelection().selectFileStart(); },
|
||||
multiSelectAction: "forEach",
|
||||
readOnly: true,
|
||||
scrollIntoView: "animate",
|
||||
aceCommandGroup: "fileJump"
|
||||
}, {
|
||||
name: "gotostart",
|
||||
description: "Go to start",
|
||||
bindKey: bindKey("Ctrl-Home", "Command-Home|Command-Up"),
|
||||
exec: function(editor) { editor.navigateFileStart(); },
|
||||
multiSelectAction: "forEach",
|
||||
readOnly: true,
|
||||
scrollIntoView: "animate",
|
||||
aceCommandGroup: "fileJump"
|
||||
}, {
|
||||
name: "selectup",
|
||||
description: "Select up",
|
||||
bindKey: bindKey("Shift-Up", "Shift-Up|Ctrl-Shift-P"),
|
||||
exec: function(editor) { editor.getSelection().selectUp(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "golineup",
|
||||
description: "Go line up",
|
||||
bindKey: bindKey("Up", "Up|Ctrl-P"),
|
||||
exec: function(editor, args) { editor.navigateUp(args.times); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selecttoend",
|
||||
description: "Select to end",
|
||||
bindKey: bindKey("Ctrl-Shift-End", "Command-Shift-End|Command-Shift-Down"),
|
||||
exec: function(editor) { editor.getSelection().selectFileEnd(); },
|
||||
multiSelectAction: "forEach",
|
||||
readOnly: true,
|
||||
scrollIntoView: "animate",
|
||||
aceCommandGroup: "fileJump"
|
||||
}, {
|
||||
name: "gotoend",
|
||||
description: "Go to end",
|
||||
bindKey: bindKey("Ctrl-End", "Command-End|Command-Down"),
|
||||
exec: function(editor) { editor.navigateFileEnd(); },
|
||||
multiSelectAction: "forEach",
|
||||
readOnly: true,
|
||||
scrollIntoView: "animate",
|
||||
aceCommandGroup: "fileJump"
|
||||
}, {
|
||||
name: "selectdown",
|
||||
description: "Select down",
|
||||
bindKey: bindKey("Shift-Down", "Shift-Down|Ctrl-Shift-N"),
|
||||
exec: function(editor) { editor.getSelection().selectDown(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "golinedown",
|
||||
description: "Go line down",
|
||||
bindKey: bindKey("Down", "Down|Ctrl-N"),
|
||||
exec: function(editor, args) { editor.navigateDown(args.times); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectwordleft",
|
||||
description: "Select word left",
|
||||
bindKey: bindKey("Ctrl-Shift-Left", "Option-Shift-Left"),
|
||||
exec: function(editor) { editor.getSelection().selectWordLeft(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "gotowordleft",
|
||||
description: "Go to word left",
|
||||
bindKey: bindKey("Ctrl-Left", "Option-Left"),
|
||||
exec: function(editor) { editor.navigateWordLeft(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selecttolinestart",
|
||||
description: "Select to line start",
|
||||
bindKey: bindKey("Alt-Shift-Left", "Command-Shift-Left|Ctrl-Shift-A"),
|
||||
exec: function(editor) { editor.getSelection().selectLineStart(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "gotolinestart",
|
||||
description: "Go to line start",
|
||||
bindKey: bindKey("Alt-Left|Home", "Command-Left|Home|Ctrl-A"),
|
||||
exec: function(editor) { editor.navigateLineStart(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectleft",
|
||||
description: "Select left",
|
||||
bindKey: bindKey("Shift-Left", "Shift-Left|Ctrl-Shift-B"),
|
||||
exec: function(editor) { editor.getSelection().selectLeft(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "gotoleft",
|
||||
description: "Go to left",
|
||||
bindKey: bindKey("Left", "Left|Ctrl-B"),
|
||||
exec: function(editor, args) { editor.navigateLeft(args.times); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectwordright",
|
||||
description: "Select word right",
|
||||
bindKey: bindKey("Ctrl-Shift-Right", "Option-Shift-Right"),
|
||||
exec: function(editor) { editor.getSelection().selectWordRight(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "gotowordright",
|
||||
description: "Go to word right",
|
||||
bindKey: bindKey("Ctrl-Right", "Option-Right"),
|
||||
exec: function(editor) { editor.navigateWordRight(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selecttolineend",
|
||||
description: "Select to line end",
|
||||
bindKey: bindKey("Alt-Shift-Right", "Command-Shift-Right|Shift-End|Ctrl-Shift-E"),
|
||||
exec: function(editor) { editor.getSelection().selectLineEnd(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "gotolineend",
|
||||
description: "Go to line end",
|
||||
bindKey: bindKey("Alt-Right|End", "Command-Right|End|Ctrl-E"),
|
||||
exec: function(editor) { editor.navigateLineEnd(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectright",
|
||||
description: "Select right",
|
||||
bindKey: bindKey("Shift-Right", "Shift-Right"),
|
||||
exec: function(editor) { editor.getSelection().selectRight(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "gotoright",
|
||||
description: "Go to right",
|
||||
bindKey: bindKey("Right", "Right|Ctrl-F"),
|
||||
exec: function(editor, args) { editor.navigateRight(args.times); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectpagedown",
|
||||
description: "Select page down",
|
||||
bindKey: "Shift-PageDown",
|
||||
exec: function(editor) { editor.selectPageDown(); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "pagedown",
|
||||
description: "Page down",
|
||||
bindKey: bindKey(null, "Option-PageDown"),
|
||||
exec: function(editor) { editor.scrollPageDown(); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "gotopagedown",
|
||||
description: "Go to page down",
|
||||
bindKey: bindKey("PageDown", "PageDown|Ctrl-V"),
|
||||
exec: function(editor) { editor.gotoPageDown(); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectpageup",
|
||||
description: "Select page up",
|
||||
bindKey: "Shift-PageUp",
|
||||
exec: function(editor) { editor.selectPageUp(); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "pageup",
|
||||
description: "Page up",
|
||||
bindKey: bindKey(null, "Option-PageUp"),
|
||||
exec: function(editor) { editor.scrollPageUp(); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "gotopageup",
|
||||
description: "Go to page up",
|
||||
bindKey: "PageUp",
|
||||
exec: function(editor) { editor.gotoPageUp(); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "scrollup",
|
||||
description: "Scroll up",
|
||||
bindKey: bindKey("Ctrl-Up", null),
|
||||
exec: function(e) { e.renderer.scrollBy(0, -2 * e.renderer.layerConfig.lineHeight); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "scrolldown",
|
||||
description: "Scroll down",
|
||||
bindKey: bindKey("Ctrl-Down", null),
|
||||
exec: function(e) { e.renderer.scrollBy(0, 2 * e.renderer.layerConfig.lineHeight); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectlinestart",
|
||||
description: "Select line start",
|
||||
bindKey: "Shift-Home",
|
||||
exec: function(editor) { editor.getSelection().selectLineStart(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectlineend",
|
||||
description: "Select line end",
|
||||
bindKey: "Shift-End",
|
||||
exec: function(editor) { editor.getSelection().selectLineEnd(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "togglerecording",
|
||||
description: "Toggle recording",
|
||||
bindKey: bindKey("Ctrl-Alt-E", "Command-Option-E"),
|
||||
exec: function(editor) { editor.commands.toggleRecording(editor); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "replaymacro",
|
||||
description: "Replay macro",
|
||||
bindKey: bindKey("Ctrl-Shift-E", "Command-Shift-E"),
|
||||
exec: function(editor) { editor.commands.replay(editor); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "jumptomatching",
|
||||
description: "Jump to matching",
|
||||
bindKey: bindKey("Ctrl-\\|Ctrl-P", "Command-\\"),
|
||||
exec: function(editor) { editor.jumpToMatching(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "animate",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selecttomatching",
|
||||
description: "Select to matching",
|
||||
bindKey: bindKey("Ctrl-Shift-\\|Ctrl-Shift-P", "Command-Shift-\\"),
|
||||
exec: function(editor) { editor.jumpToMatching(true); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "animate",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "expandToMatching",
|
||||
description: "Expand to matching",
|
||||
bindKey: bindKey("Ctrl-Shift-M", "Ctrl-Shift-M"),
|
||||
exec: function(editor) { editor.jumpToMatching(true, true); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "animate",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "passKeysToBrowser",
|
||||
description: "Pass keys to browser",
|
||||
bindKey: bindKey(null, null),
|
||||
exec: function() {},
|
||||
passEvent: true,
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "copy",
|
||||
description: "Copy",
|
||||
exec: function(editor) {
|
||||
// placeholder for replay macro
|
||||
},
|
||||
readOnly: true
|
||||
},
|
||||
|
||||
// commands disabled in readOnly mode
|
||||
{
|
||||
name: "cut",
|
||||
description: "Cut",
|
||||
exec: function(editor) {
|
||||
var cutLine = editor.$copyWithEmptySelection && editor.selection.isEmpty();
|
||||
var range = cutLine ? editor.selection.getLineRange() : editor.selection.getRange();
|
||||
editor._emit("cut", range);
|
||||
|
||||
if (!range.isEmpty())
|
||||
editor.session.remove(range);
|
||||
editor.clearSelection();
|
||||
},
|
||||
scrollIntoView: "cursor",
|
||||
multiSelectAction: "forEach"
|
||||
}, {
|
||||
name: "paste",
|
||||
description: "Paste",
|
||||
exec: function(editor, args) {
|
||||
editor.$handlePaste(args);
|
||||
},
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "removeline",
|
||||
description: "Remove line",
|
||||
bindKey: bindKey("Ctrl-D", "Command-D"),
|
||||
exec: function(editor) { editor.removeLines(); },
|
||||
scrollIntoView: "cursor",
|
||||
multiSelectAction: "forEachLine"
|
||||
}, {
|
||||
name: "duplicateSelection",
|
||||
description: "Duplicate selection",
|
||||
bindKey: bindKey("Ctrl-Shift-D", "Command-Shift-D"),
|
||||
exec: function(editor) { editor.duplicateSelection(); },
|
||||
scrollIntoView: "cursor",
|
||||
multiSelectAction: "forEach"
|
||||
}, {
|
||||
name: "sortlines",
|
||||
description: "Sort lines",
|
||||
bindKey: bindKey("Ctrl-Alt-S", "Command-Alt-S"),
|
||||
exec: function(editor) { editor.sortLines(); },
|
||||
scrollIntoView: "selection",
|
||||
multiSelectAction: "forEachLine"
|
||||
}, {
|
||||
name: "togglecomment",
|
||||
description: "Toggle comment",
|
||||
bindKey: bindKey("Ctrl-/", "Command-/"),
|
||||
exec: function(editor) { editor.toggleCommentLines(); },
|
||||
multiSelectAction: "forEachLine",
|
||||
scrollIntoView: "selectionPart"
|
||||
}, {
|
||||
name: "toggleBlockComment",
|
||||
description: "Toggle block comment",
|
||||
bindKey: bindKey("Ctrl-Shift-/", "Command-Shift-/"),
|
||||
exec: function(editor) { editor.toggleBlockComment(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "selectionPart"
|
||||
}, {
|
||||
name: "modifyNumberUp",
|
||||
description: "Modify number up",
|
||||
bindKey: bindKey("Ctrl-Shift-Up", "Alt-Shift-Up"),
|
||||
exec: function(editor) { editor.modifyNumber(1); },
|
||||
scrollIntoView: "cursor",
|
||||
multiSelectAction: "forEach"
|
||||
}, {
|
||||
name: "modifyNumberDown",
|
||||
description: "Modify number down",
|
||||
bindKey: bindKey("Ctrl-Shift-Down", "Alt-Shift-Down"),
|
||||
exec: function(editor) { editor.modifyNumber(-1); },
|
||||
scrollIntoView: "cursor",
|
||||
multiSelectAction: "forEach"
|
||||
}, {
|
||||
name: "replace",
|
||||
description: "Replace",
|
||||
bindKey: bindKey("Ctrl-H", "Command-Option-F"),
|
||||
exec: function(editor) {
|
||||
config.loadModule("ace/ext/searchbox", function(e) {e.Search(editor, true);});
|
||||
}
|
||||
}, {
|
||||
name: "undo",
|
||||
description: "Undo",
|
||||
bindKey: bindKey("Ctrl-Z", "Command-Z"),
|
||||
exec: function(editor) { editor.undo(); }
|
||||
}, {
|
||||
name: "redo",
|
||||
description: "Redo",
|
||||
bindKey: bindKey("Ctrl-Shift-Z|Ctrl-Y", "Command-Shift-Z|Command-Y"),
|
||||
exec: function(editor) { editor.redo(); }
|
||||
}, {
|
||||
name: "copylinesup",
|
||||
description: "Copy lines up",
|
||||
bindKey: bindKey("Alt-Shift-Up", "Command-Option-Up"),
|
||||
exec: function(editor) { editor.copyLinesUp(); },
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "movelinesup",
|
||||
description: "Move lines up",
|
||||
bindKey: bindKey("Alt-Up", "Option-Up"),
|
||||
exec: function(editor) { editor.moveLinesUp(); },
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "copylinesdown",
|
||||
description: "Copy lines down",
|
||||
bindKey: bindKey("Alt-Shift-Down", "Command-Option-Down"),
|
||||
exec: function(editor) { editor.copyLinesDown(); },
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "movelinesdown",
|
||||
description: "Move lines down",
|
||||
bindKey: bindKey("Alt-Down", "Option-Down"),
|
||||
exec: function(editor) { editor.moveLinesDown(); },
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "del",
|
||||
description: "Delete",
|
||||
bindKey: bindKey("Delete", "Delete|Ctrl-D|Shift-Delete"),
|
||||
exec: function(editor) { editor.remove("right"); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "backspace",
|
||||
description: "Backspace",
|
||||
bindKey: bindKey(
|
||||
"Shift-Backspace|Backspace",
|
||||
"Ctrl-Backspace|Shift-Backspace|Backspace|Ctrl-H"
|
||||
),
|
||||
exec: function(editor) { editor.remove("left"); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "cut_or_delete",
|
||||
description: "Cut or delete",
|
||||
bindKey: bindKey("Shift-Delete", null),
|
||||
exec: function(editor) {
|
||||
if (editor.selection.isEmpty()) {
|
||||
editor.remove("left");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "removetolinestart",
|
||||
description: "Remove to line start",
|
||||
bindKey: bindKey("Alt-Backspace", "Command-Backspace"),
|
||||
exec: function(editor) { editor.removeToLineStart(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "removetolineend",
|
||||
description: "Remove to line end",
|
||||
bindKey: bindKey("Alt-Delete", "Ctrl-K|Command-Delete"),
|
||||
exec: function(editor) { editor.removeToLineEnd(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "removetolinestarthard",
|
||||
description: "Remove to line start hard",
|
||||
bindKey: bindKey("Ctrl-Shift-Backspace", null),
|
||||
exec: function(editor) {
|
||||
var range = editor.selection.getRange();
|
||||
range.start.column = 0;
|
||||
editor.session.remove(range);
|
||||
},
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "removetolineendhard",
|
||||
description: "Remove to line end hard",
|
||||
bindKey: bindKey("Ctrl-Shift-Delete", null),
|
||||
exec: function(editor) {
|
||||
var range = editor.selection.getRange();
|
||||
range.end.column = Number.MAX_VALUE;
|
||||
editor.session.remove(range);
|
||||
},
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "removewordleft",
|
||||
description: "Remove word left",
|
||||
bindKey: bindKey("Ctrl-Backspace", "Alt-Backspace|Ctrl-Alt-Backspace"),
|
||||
exec: function(editor) { editor.removeWordLeft(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "removewordright",
|
||||
description: "Remove word right",
|
||||
bindKey: bindKey("Ctrl-Delete", "Alt-Delete"),
|
||||
exec: function(editor) { editor.removeWordRight(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "outdent",
|
||||
description: "Outdent",
|
||||
bindKey: bindKey("Shift-Tab", "Shift-Tab"),
|
||||
exec: function(editor) { editor.blockOutdent(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "selectionPart"
|
||||
}, {
|
||||
name: "indent",
|
||||
description: "Indent",
|
||||
bindKey: bindKey("Tab", "Tab"),
|
||||
exec: function(editor) { editor.indent(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "selectionPart"
|
||||
}, {
|
||||
name: "blockoutdent",
|
||||
description: "Block outdent",
|
||||
bindKey: bindKey("Ctrl-[", "Ctrl-["),
|
||||
exec: function(editor) { editor.blockOutdent(); },
|
||||
multiSelectAction: "forEachLine",
|
||||
scrollIntoView: "selectionPart"
|
||||
}, {
|
||||
name: "blockindent",
|
||||
description: "Block indent",
|
||||
bindKey: bindKey("Ctrl-]", "Ctrl-]"),
|
||||
exec: function(editor) { editor.blockIndent(); },
|
||||
multiSelectAction: "forEachLine",
|
||||
scrollIntoView: "selectionPart"
|
||||
}, {
|
||||
name: "insertstring",
|
||||
description: "Insert string",
|
||||
exec: function(editor, str) { editor.insert(str); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "inserttext",
|
||||
description: "Insert text",
|
||||
exec: function(editor, args) {
|
||||
editor.insert(lang.stringRepeat(args.text || "", args.times || 1));
|
||||
},
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "splitline",
|
||||
description: "Split line",
|
||||
bindKey: bindKey(null, "Ctrl-O"),
|
||||
exec: function(editor) { editor.splitLine(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "transposeletters",
|
||||
description: "Transpose letters",
|
||||
bindKey: bindKey("Alt-Shift-X", "Ctrl-T"),
|
||||
exec: function(editor) { editor.transposeLetters(); },
|
||||
multiSelectAction: function(editor) {editor.transposeSelections(1); },
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "touppercase",
|
||||
description: "To uppercase",
|
||||
bindKey: bindKey("Ctrl-U", "Ctrl-U"),
|
||||
exec: function(editor) { editor.toUpperCase(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "tolowercase",
|
||||
description: "To lowercase",
|
||||
bindKey: bindKey("Ctrl-Shift-U", "Ctrl-Shift-U"),
|
||||
exec: function(editor) { editor.toLowerCase(); },
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "expandtoline",
|
||||
description: "Expand to line",
|
||||
bindKey: bindKey("Ctrl-Shift-L", "Command-Shift-L"),
|
||||
exec: function(editor) {
|
||||
var range = editor.selection.getRange();
|
||||
|
||||
range.start.column = range.end.column = 0;
|
||||
range.end.row++;
|
||||
editor.selection.setRange(range, false);
|
||||
},
|
||||
multiSelectAction: "forEach",
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "joinlines",
|
||||
description: "Join lines",
|
||||
bindKey: bindKey(null, null),
|
||||
exec: function(editor) {
|
||||
var isBackwards = editor.selection.isBackwards();
|
||||
var selectionStart = isBackwards ? editor.selection.getSelectionLead() : editor.selection.getSelectionAnchor();
|
||||
var selectionEnd = isBackwards ? editor.selection.getSelectionAnchor() : editor.selection.getSelectionLead();
|
||||
var firstLineEndCol = editor.session.doc.getLine(selectionStart.row).length;
|
||||
var selectedText = editor.session.doc.getTextRange(editor.selection.getRange());
|
||||
var selectedCount = selectedText.replace(/\n\s*/, " ").length;
|
||||
var insertLine = editor.session.doc.getLine(selectionStart.row);
|
||||
|
||||
for (var i = selectionStart.row + 1; i <= selectionEnd.row + 1; i++) {
|
||||
var curLine = lang.stringTrimLeft(lang.stringTrimRight(editor.session.doc.getLine(i)));
|
||||
if (curLine.length !== 0) {
|
||||
curLine = " " + curLine;
|
||||
}
|
||||
insertLine += curLine;
|
||||
}
|
||||
|
||||
if (selectionEnd.row + 1 < (editor.session.doc.getLength() - 1)) {
|
||||
// Don't insert a newline at the end of the document
|
||||
insertLine += editor.session.doc.getNewLineCharacter();
|
||||
}
|
||||
|
||||
editor.clearSelection();
|
||||
editor.session.doc.replace(new Range(selectionStart.row, 0, selectionEnd.row + 2, 0), insertLine);
|
||||
|
||||
if (selectedCount > 0) {
|
||||
// Select the text that was previously selected
|
||||
editor.selection.moveCursorTo(selectionStart.row, selectionStart.column);
|
||||
editor.selection.selectTo(selectionStart.row, selectionStart.column + selectedCount);
|
||||
} else {
|
||||
// If the joined line had something in it, start the cursor at that something
|
||||
firstLineEndCol = editor.session.doc.getLine(selectionStart.row).length > firstLineEndCol ? (firstLineEndCol + 1) : firstLineEndCol;
|
||||
editor.selection.moveCursorTo(selectionStart.row, firstLineEndCol);
|
||||
}
|
||||
},
|
||||
multiSelectAction: "forEach",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "invertSelection",
|
||||
description: "Invert selection",
|
||||
bindKey: bindKey(null, null),
|
||||
exec: function(editor) {
|
||||
var endRow = editor.session.doc.getLength() - 1;
|
||||
var endCol = editor.session.doc.getLine(endRow).length;
|
||||
var ranges = editor.selection.rangeList.ranges;
|
||||
var newRanges = [];
|
||||
|
||||
// If multiple selections don't exist, rangeList will return 0 so replace with single range
|
||||
if (ranges.length < 1) {
|
||||
ranges = [editor.selection.getRange()];
|
||||
}
|
||||
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
if (i == (ranges.length - 1)) {
|
||||
// The last selection must connect to the end of the document, unless it already does
|
||||
if (!(ranges[i].end.row === endRow && ranges[i].end.column === endCol)) {
|
||||
newRanges.push(new Range(ranges[i].end.row, ranges[i].end.column, endRow, endCol));
|
||||
}
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
// The first selection must connect to the start of the document, unless it already does
|
||||
if (!(ranges[i].start.row === 0 && ranges[i].start.column === 0)) {
|
||||
newRanges.push(new Range(0, 0, ranges[i].start.row, ranges[i].start.column));
|
||||
}
|
||||
} else {
|
||||
newRanges.push(new Range(ranges[i-1].end.row, ranges[i-1].end.column, ranges[i].start.row, ranges[i].start.column));
|
||||
}
|
||||
}
|
||||
|
||||
editor.exitMultiSelectMode();
|
||||
editor.clearSelection();
|
||||
|
||||
for(var i = 0; i < newRanges.length; i++) {
|
||||
editor.selection.addRange(newRanges[i], false);
|
||||
}
|
||||
},
|
||||
readOnly: true,
|
||||
scrollIntoView: "none"
|
||||
}, {
|
||||
name: "openCommandPallete",
|
||||
description: "Open command pallete",
|
||||
bindKey: bindKey("F1", "F1"),
|
||||
exec: function(editor) {
|
||||
editor.prompt({ $type: "commands" });
|
||||
},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "modeSelect",
|
||||
description: "Change language mode...",
|
||||
bindKey: bindKey(null, null),
|
||||
exec: function(editor) {
|
||||
editor.prompt({ $type: "modes" });
|
||||
},
|
||||
readOnly: true
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -1,214 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
var config = require("../config");
|
||||
var oop = require("../lib/oop");
|
||||
var HashHandler = require("../keyboard/hash_handler").HashHandler;
|
||||
var occurStartCommand = require("./occur_commands").occurStartCommand;
|
||||
|
||||
// These commands can be installed in a normal key handler to start iSearch:
|
||||
exports.iSearchStartCommands = [{
|
||||
name: "iSearch",
|
||||
bindKey: {win: "Ctrl-F", mac: "Command-F"},
|
||||
exec: function(editor, options) {
|
||||
config.loadModule(["core", "ace/incremental_search"], function(e) {
|
||||
var iSearch = e.iSearch = e.iSearch || new e.IncrementalSearch();
|
||||
iSearch.activate(editor, options.backwards);
|
||||
if (options.jumpToFirstMatch) iSearch.next(options);
|
||||
});
|
||||
},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "iSearchBackwards",
|
||||
exec: function(editor, jumpToNext) { editor.execCommand('iSearch', {backwards: true}); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "iSearchAndGo",
|
||||
bindKey: {win: "Ctrl-K", mac: "Command-G"},
|
||||
exec: function(editor, jumpToNext) { editor.execCommand('iSearch', {jumpToFirstMatch: true, useCurrentOrPrevSearch: true}); },
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "iSearchBackwardsAndGo",
|
||||
bindKey: {win: "Ctrl-Shift-K", mac: "Command-Shift-G"},
|
||||
exec: function(editor) { editor.execCommand('iSearch', {jumpToFirstMatch: true, backwards: true, useCurrentOrPrevSearch: true}); },
|
||||
readOnly: true
|
||||
}];
|
||||
|
||||
// These commands are only available when incremental search mode is active:
|
||||
exports.iSearchCommands = [{
|
||||
name: "restartSearch",
|
||||
bindKey: {win: "Ctrl-F", mac: "Command-F"},
|
||||
exec: function(iSearch) {
|
||||
iSearch.cancelSearch(true);
|
||||
}
|
||||
}, {
|
||||
name: "searchForward",
|
||||
bindKey: {win: "Ctrl-S|Ctrl-K", mac: "Ctrl-S|Command-G"},
|
||||
exec: function(iSearch, options) {
|
||||
options.useCurrentOrPrevSearch = true;
|
||||
iSearch.next(options);
|
||||
}
|
||||
}, {
|
||||
name: "searchBackward",
|
||||
bindKey: {win: "Ctrl-R|Ctrl-Shift-K", mac: "Ctrl-R|Command-Shift-G"},
|
||||
exec: function(iSearch, options) {
|
||||
options.useCurrentOrPrevSearch = true;
|
||||
options.backwards = true;
|
||||
iSearch.next(options);
|
||||
}
|
||||
}, {
|
||||
name: "extendSearchTerm",
|
||||
exec: function(iSearch, string) {
|
||||
iSearch.addString(string);
|
||||
}
|
||||
}, {
|
||||
name: "extendSearchTermSpace",
|
||||
bindKey: "space",
|
||||
exec: function(iSearch) { iSearch.addString(' '); }
|
||||
}, {
|
||||
name: "shrinkSearchTerm",
|
||||
bindKey: "backspace",
|
||||
exec: function(iSearch) {
|
||||
iSearch.removeChar();
|
||||
}
|
||||
}, {
|
||||
name: 'confirmSearch',
|
||||
bindKey: 'return',
|
||||
exec: function(iSearch) { iSearch.deactivate(); }
|
||||
}, {
|
||||
name: 'cancelSearch',
|
||||
bindKey: 'esc|Ctrl-G',
|
||||
exec: function(iSearch) { iSearch.deactivate(true); }
|
||||
}, {
|
||||
name: 'occurisearch',
|
||||
bindKey: 'Ctrl-O',
|
||||
exec: function(iSearch) {
|
||||
var options = oop.mixin({}, iSearch.$options);
|
||||
iSearch.deactivate();
|
||||
occurStartCommand.exec(iSearch.$editor, options);
|
||||
}
|
||||
}, {
|
||||
name: "yankNextWord",
|
||||
bindKey: "Ctrl-w",
|
||||
exec: function(iSearch) {
|
||||
var ed = iSearch.$editor,
|
||||
range = ed.selection.getRangeOfMovements(function(sel) { sel.moveCursorWordRight(); }),
|
||||
string = ed.session.getTextRange(range);
|
||||
iSearch.addString(string);
|
||||
}
|
||||
}, {
|
||||
name: "yankNextChar",
|
||||
bindKey: "Ctrl-Alt-y",
|
||||
exec: function(iSearch) {
|
||||
var ed = iSearch.$editor,
|
||||
range = ed.selection.getRangeOfMovements(function(sel) { sel.moveCursorRight(); }),
|
||||
string = ed.session.getTextRange(range);
|
||||
iSearch.addString(string);
|
||||
}
|
||||
}, {
|
||||
name: 'recenterTopBottom',
|
||||
bindKey: 'Ctrl-l',
|
||||
exec: function(iSearch) { iSearch.$editor.execCommand('recenterTopBottom'); }
|
||||
}, {
|
||||
name: 'selectAllMatches',
|
||||
bindKey: 'Ctrl-space',
|
||||
exec: function(iSearch) {
|
||||
var ed = iSearch.$editor,
|
||||
hl = ed.session.$isearchHighlight,
|
||||
ranges = hl && hl.cache ? hl.cache
|
||||
.reduce(function(ranges, ea) {
|
||||
return ranges.concat(ea ? ea : []); }, []) : [];
|
||||
iSearch.deactivate(false);
|
||||
ranges.forEach(ed.selection.addRange.bind(ed.selection));
|
||||
}
|
||||
}, {
|
||||
name: 'searchAsRegExp',
|
||||
bindKey: 'Alt-r',
|
||||
exec: function(iSearch) {
|
||||
iSearch.convertNeedleToRegExp();
|
||||
}
|
||||
}].map(function(cmd) {
|
||||
cmd.readOnly = true;
|
||||
cmd.isIncrementalSearchCommand = true;
|
||||
cmd.scrollIntoView = "animate-cursor";
|
||||
return cmd;
|
||||
});
|
||||
|
||||
function IncrementalSearchKeyboardHandler(iSearch) {
|
||||
this.$iSearch = iSearch;
|
||||
}
|
||||
|
||||
oop.inherits(IncrementalSearchKeyboardHandler, HashHandler);
|
||||
|
||||
(function() {
|
||||
|
||||
this.attach = function(editor) {
|
||||
var iSearch = this.$iSearch;
|
||||
HashHandler.call(this, exports.iSearchCommands, editor.commands.platform);
|
||||
this.$commandExecHandler = editor.commands.addEventListener('exec', function(e) {
|
||||
if (!e.command.isIncrementalSearchCommand)
|
||||
return iSearch.deactivate();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
var scrollTop = editor.session.getScrollTop();
|
||||
var result = e.command.exec(iSearch, e.args || {});
|
||||
editor.renderer.scrollCursorIntoView(null, 0.5);
|
||||
editor.renderer.animateScrolling(scrollTop);
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
this.detach = function(editor) {
|
||||
if (!this.$commandExecHandler) return;
|
||||
editor.commands.removeEventListener('exec', this.$commandExecHandler);
|
||||
delete this.$commandExecHandler;
|
||||
};
|
||||
|
||||
var handleKeyboard$super = this.handleKeyboard;
|
||||
this.handleKeyboard = function(data, hashId, key, keyCode) {
|
||||
if (((hashId === 1/*ctrl*/ || hashId === 8/*command*/) && key === 'v')
|
||||
|| (hashId === 1/*ctrl*/ && key === 'y')) return null;
|
||||
var cmd = handleKeyboard$super.call(this, data, hashId, key, keyCode);
|
||||
if (cmd.command) { return cmd; }
|
||||
if (hashId == -1) {
|
||||
var extendCmd = this.commands.extendSearchTerm;
|
||||
if (extendCmd) { return {command: extendCmd, args: key}; }
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
}).call(IncrementalSearchKeyboardHandler.prototype);
|
||||
|
||||
|
||||
exports.IncrementalSearchKeyboardHandler = IncrementalSearchKeyboardHandler;
|
||||
|
||||
});
|
||||
@@ -1,125 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// commands to enter multiselect mode
|
||||
exports.defaultCommands = [{
|
||||
name: "addCursorAbove",
|
||||
description: "Add cursor above",
|
||||
exec: function(editor) { editor.selectMoreLines(-1); },
|
||||
bindKey: {win: "Ctrl-Alt-Up", mac: "Ctrl-Alt-Up"},
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "addCursorBelow",
|
||||
description: "Add cursor below",
|
||||
exec: function(editor) { editor.selectMoreLines(1); },
|
||||
bindKey: {win: "Ctrl-Alt-Down", mac: "Ctrl-Alt-Down"},
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "addCursorAboveSkipCurrent",
|
||||
description: "Add cursor above (skip current)",
|
||||
exec: function(editor) { editor.selectMoreLines(-1, true); },
|
||||
bindKey: {win: "Ctrl-Alt-Shift-Up", mac: "Ctrl-Alt-Shift-Up"},
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "addCursorBelowSkipCurrent",
|
||||
description: "Add cursor below (skip current)",
|
||||
exec: function(editor) { editor.selectMoreLines(1, true); },
|
||||
bindKey: {win: "Ctrl-Alt-Shift-Down", mac: "Ctrl-Alt-Shift-Down"},
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectMoreBefore",
|
||||
description: "Select more before",
|
||||
exec: function(editor) { editor.selectMore(-1); },
|
||||
bindKey: {win: "Ctrl-Alt-Left", mac: "Ctrl-Alt-Left"},
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectMoreAfter",
|
||||
description: "Select more after",
|
||||
exec: function(editor) { editor.selectMore(1); },
|
||||
bindKey: {win: "Ctrl-Alt-Right", mac: "Ctrl-Alt-Right"},
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectNextBefore",
|
||||
description: "Select next before",
|
||||
exec: function(editor) { editor.selectMore(-1, true); },
|
||||
bindKey: {win: "Ctrl-Alt-Shift-Left", mac: "Ctrl-Alt-Shift-Left"},
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "selectNextAfter",
|
||||
description: "Select next after",
|
||||
exec: function(editor) { editor.selectMore(1, true); },
|
||||
bindKey: {win: "Ctrl-Alt-Shift-Right", mac: "Ctrl-Alt-Shift-Right"},
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "splitIntoLines",
|
||||
description: "Split into lines",
|
||||
exec: function(editor) { editor.multiSelect.splitIntoLines(); },
|
||||
bindKey: {win: "Ctrl-Alt-L", mac: "Ctrl-Alt-L"},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "alignCursors",
|
||||
description: "Align cursors",
|
||||
exec: function(editor) { editor.alignCursors(); },
|
||||
bindKey: {win: "Ctrl-Alt-A", mac: "Ctrl-Alt-A"},
|
||||
scrollIntoView: "cursor"
|
||||
}, {
|
||||
name: "findAll",
|
||||
description: "Find all",
|
||||
exec: function(editor) { editor.findAll(); },
|
||||
bindKey: {win: "Ctrl-Alt-K", mac: "Ctrl-Alt-G"},
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true
|
||||
}];
|
||||
|
||||
// commands active only in multiselect mode
|
||||
exports.multiSelectCommands = [{
|
||||
name: "singleSelection",
|
||||
description: "Single selection",
|
||||
bindKey: "esc",
|
||||
exec: function(editor) { editor.exitMultiSelectMode(); },
|
||||
scrollIntoView: "cursor",
|
||||
readOnly: true,
|
||||
isAvailable: function(editor) {return editor && editor.inMultiSelectMode;}
|
||||
}];
|
||||
|
||||
var HashHandler = require("../keyboard/hash_handler").HashHandler;
|
||||
exports.keyboardHandler = new HashHandler(exports.multiSelectCommands);
|
||||
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
var config = require("../config"),
|
||||
Occur = require("../occur").Occur;
|
||||
|
||||
// These commands can be installed in a normal command handler to start occur:
|
||||
var occurStartCommand = {
|
||||
name: "occur",
|
||||
exec: function(editor, options) {
|
||||
var alreadyInOccur = !!editor.session.$occur;
|
||||
var occurSessionActive = new Occur().enter(editor, options);
|
||||
if (occurSessionActive && !alreadyInOccur)
|
||||
OccurKeyboardHandler.installIn(editor);
|
||||
},
|
||||
readOnly: true
|
||||
};
|
||||
|
||||
var occurCommands = [{
|
||||
name: "occurexit",
|
||||
bindKey: 'esc|Ctrl-G',
|
||||
exec: function(editor) {
|
||||
var occur = editor.session.$occur;
|
||||
if (!occur) return;
|
||||
occur.exit(editor, {});
|
||||
if (!editor.session.$occur) OccurKeyboardHandler.uninstallFrom(editor);
|
||||
},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "occuraccept",
|
||||
bindKey: 'enter',
|
||||
exec: function(editor) {
|
||||
var occur = editor.session.$occur;
|
||||
if (!occur) return;
|
||||
occur.exit(editor, {translatePosition: true});
|
||||
if (!editor.session.$occur) OccurKeyboardHandler.uninstallFrom(editor);
|
||||
},
|
||||
readOnly: true
|
||||
}];
|
||||
|
||||
var HashHandler = require("../keyboard/hash_handler").HashHandler;
|
||||
var oop = require("../lib/oop");
|
||||
|
||||
|
||||
function OccurKeyboardHandler() {}
|
||||
|
||||
oop.inherits(OccurKeyboardHandler, HashHandler);
|
||||
|
||||
(function() {
|
||||
|
||||
this.isOccurHandler = true;
|
||||
|
||||
this.attach = function(editor) {
|
||||
HashHandler.call(this, occurCommands, editor.commands.platform);
|
||||
this.$editor = editor;
|
||||
};
|
||||
|
||||
var handleKeyboard$super = this.handleKeyboard;
|
||||
this.handleKeyboard = function(data, hashId, key, keyCode) {
|
||||
var cmd = handleKeyboard$super.call(this, data, hashId, key, keyCode);
|
||||
return (cmd && cmd.command) ? cmd : undefined;
|
||||
};
|
||||
|
||||
}).call(OccurKeyboardHandler.prototype);
|
||||
|
||||
OccurKeyboardHandler.installIn = function(editor) {
|
||||
var handler = new this();
|
||||
editor.keyBinding.addKeyboardHandler(handler);
|
||||
editor.commands.addCommands(occurCommands);
|
||||
};
|
||||
|
||||
OccurKeyboardHandler.uninstallFrom = function(editor) {
|
||||
editor.commands.removeCommands(occurCommands);
|
||||
var handler = editor.getKeyboardHandler();
|
||||
if (handler.isOccurHandler)
|
||||
editor.keyBinding.removeKeyboardHandler(handler);
|
||||
};
|
||||
|
||||
exports.occurStartCommand = occurStartCommand;
|
||||
|
||||
});
|
||||
@@ -1,225 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"no use strict";
|
||||
|
||||
var lang = require("./lib/lang");
|
||||
var oop = require("./lib/oop");
|
||||
var net = require("./lib/net");
|
||||
var AppConfig = require("./lib/app_config").AppConfig;
|
||||
|
||||
module.exports = exports = new AppConfig();
|
||||
|
||||
var global = (function() {
|
||||
return this || typeof window != "undefined" && window;
|
||||
})();
|
||||
|
||||
var options = {
|
||||
packaged: false,
|
||||
workerPath: null,
|
||||
modePath: null,
|
||||
themePath: null,
|
||||
basePath: "",
|
||||
suffix: ".js",
|
||||
$moduleUrls: {},
|
||||
loadWorkerFromBlob: true,
|
||||
sharedPopups: false
|
||||
};
|
||||
|
||||
exports.get = function(key) {
|
||||
if (!options.hasOwnProperty(key))
|
||||
throw new Error("Unknown config key: " + key);
|
||||
|
||||
return options[key];
|
||||
};
|
||||
|
||||
exports.set = function(key, value) {
|
||||
if (options.hasOwnProperty(key))
|
||||
options[key] = value;
|
||||
else if (this.setDefaultValue("", key, value) == false)
|
||||
throw new Error("Unknown config key: " + key);
|
||||
};
|
||||
|
||||
exports.all = function() {
|
||||
return lang.copyObject(options);
|
||||
};
|
||||
|
||||
exports.$modes = {};
|
||||
|
||||
// module loading
|
||||
exports.moduleUrl = function(name, component) {
|
||||
if (options.$moduleUrls[name])
|
||||
return options.$moduleUrls[name];
|
||||
|
||||
var parts = name.split("/");
|
||||
component = component || parts[parts.length - 2] || "";
|
||||
|
||||
// todo make this configurable or get rid of '-'
|
||||
var sep = component == "snippets" ? "/" : "-";
|
||||
var base = parts[parts.length - 1];
|
||||
if (component == "worker" && sep == "-") {
|
||||
var re = new RegExp("^" + component + "[\\-_]|[\\-_]" + component + "$", "g");
|
||||
base = base.replace(re, "");
|
||||
}
|
||||
|
||||
if ((!base || base == component) && parts.length > 1)
|
||||
base = parts[parts.length - 2];
|
||||
var path = options[component + "Path"];
|
||||
if (path == null) {
|
||||
path = options.basePath;
|
||||
} else if (sep == "/") {
|
||||
component = sep = "";
|
||||
}
|
||||
if (path && path.slice(-1) != "/")
|
||||
path += "/";
|
||||
return path + component + sep + base + this.get("suffix");
|
||||
};
|
||||
|
||||
exports.setModuleUrl = function(name, subst) {
|
||||
return options.$moduleUrls[name] = subst;
|
||||
};
|
||||
|
||||
exports.$loading = {};
|
||||
exports.loadModule = function(moduleName, onLoad) {
|
||||
var module, moduleType;
|
||||
if (Array.isArray(moduleName)) {
|
||||
moduleType = moduleName[0];
|
||||
moduleName = moduleName[1];
|
||||
}
|
||||
|
||||
try {
|
||||
module = require(moduleName);
|
||||
} catch (e) {}
|
||||
// require(moduleName) can return empty object if called after require([moduleName], callback)
|
||||
if (module && !exports.$loading[moduleName])
|
||||
return onLoad && onLoad(module);
|
||||
|
||||
if (!exports.$loading[moduleName])
|
||||
exports.$loading[moduleName] = [];
|
||||
|
||||
exports.$loading[moduleName].push(onLoad);
|
||||
|
||||
if (exports.$loading[moduleName].length > 1)
|
||||
return;
|
||||
|
||||
var afterLoad = function() {
|
||||
require([moduleName], function(module) {
|
||||
exports._emit("load.module", {name: moduleName, module: module});
|
||||
var listeners = exports.$loading[moduleName];
|
||||
exports.$loading[moduleName] = null;
|
||||
listeners.forEach(function(onLoad) {
|
||||
onLoad && onLoad(module);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (!exports.get("packaged"))
|
||||
return afterLoad();
|
||||
|
||||
net.loadScript(exports.moduleUrl(moduleName, moduleType), afterLoad);
|
||||
reportErrorIfPathIsNotConfigured();
|
||||
};
|
||||
|
||||
var reportErrorIfPathIsNotConfigured = function() {
|
||||
if (
|
||||
!options.basePath && !options.workerPath
|
||||
&& !options.modePath && !options.themePath
|
||||
&& !Object.keys(options.$moduleUrls).length
|
||||
) {
|
||||
console.error(
|
||||
"Unable to infer path to ace from script src,",
|
||||
"use ace.config.set('basePath', 'path') to enable dynamic loading of modes and themes",
|
||||
"or with webpack use ace/webpack-resolver"
|
||||
);
|
||||
reportErrorIfPathIsNotConfigured = function() {};
|
||||
}
|
||||
};
|
||||
|
||||
// initialization
|
||||
function init(packaged) {
|
||||
if (!global || !global.document)
|
||||
return;
|
||||
|
||||
options.packaged = packaged || require.packaged || module.packaged || (global.define && define.packaged);
|
||||
|
||||
var scriptOptions = {};
|
||||
var scriptUrl = "";
|
||||
|
||||
// Use currentScript.ownerDocument in case this file was loaded from imported document. (HTML Imports)
|
||||
var currentScript = (document.currentScript || document._currentScript ); // native or polyfill
|
||||
var currentDocument = currentScript && currentScript.ownerDocument || document;
|
||||
|
||||
var scripts = currentDocument.getElementsByTagName("script");
|
||||
for (var i=0; i<scripts.length; i++) {
|
||||
var script = scripts[i];
|
||||
|
||||
var src = script.src || script.getAttribute("src");
|
||||
if (!src)
|
||||
continue;
|
||||
|
||||
var attributes = script.attributes;
|
||||
for (var j=0, l=attributes.length; j < l; j++) {
|
||||
var attr = attributes[j];
|
||||
if (attr.name.indexOf("data-ace-") === 0) {
|
||||
scriptOptions[deHyphenate(attr.name.replace(/^data-ace-/, ""))] = attr.value;
|
||||
}
|
||||
}
|
||||
|
||||
var m = src.match(/^(.*)\/ace(\-\w+)?\.js(\?|$)/);
|
||||
if (m)
|
||||
scriptUrl = m[1];
|
||||
}
|
||||
|
||||
if (scriptUrl) {
|
||||
scriptOptions.base = scriptOptions.base || scriptUrl;
|
||||
scriptOptions.packaged = true;
|
||||
}
|
||||
|
||||
scriptOptions.basePath = scriptOptions.base;
|
||||
scriptOptions.workerPath = scriptOptions.workerPath || scriptOptions.base;
|
||||
scriptOptions.modePath = scriptOptions.modePath || scriptOptions.base;
|
||||
scriptOptions.themePath = scriptOptions.themePath || scriptOptions.base;
|
||||
delete scriptOptions.base;
|
||||
|
||||
for (var key in scriptOptions)
|
||||
if (typeof scriptOptions[key] !== "undefined")
|
||||
exports.set(key, scriptOptions[key]);
|
||||
}
|
||||
|
||||
exports.init = init;
|
||||
|
||||
function deHyphenate(str) {
|
||||
return str.replace(/-(.)/g, function(m, m1) { return m1.toUpperCase(); });
|
||||
}
|
||||
|
||||
exports.version = "1.4.5";
|
||||
|
||||
});
|
||||
@@ -1,135 +0,0 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Distributed under the BSD license:
|
||||
*
|
||||
* Copyright (c) 2010, Ajax.org B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Ajax.org B.V. nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
if (typeof process !== "undefined") {
|
||||
require("amd-loader");
|
||||
}
|
||||
|
||||
define(function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var config = require("./config");
|
||||
var assert = require("./test/assertions");
|
||||
|
||||
module.exports = {
|
||||
|
||||
"test: path resolution" : function() {
|
||||
config.set("packaged", "true");
|
||||
var url = config.moduleUrl("kr_theme", "theme");
|
||||
assert.equal(url, "theme-kr_theme.js");
|
||||
|
||||
config.set("basePath", "a/b");
|
||||
url = config.moduleUrl("m/theme", "theme");
|
||||
assert.equal(url, "a/b/theme-m.js");
|
||||
|
||||
url = config.moduleUrl("m/theme", "ext");
|
||||
assert.equal(url, "a/b/ext-theme.js");
|
||||
|
||||
config.set("workerPath", "c/");
|
||||
url = config.moduleUrl("foo/1", "worker");
|
||||
assert.equal(url, "c/worker-1.js");
|
||||
|
||||
config.setModuleUrl("foo/1", "a/b1.js");
|
||||
url = config.moduleUrl("foo/1", "theme");
|
||||
assert.equal(url, "a/b1.js");
|
||||
|
||||
url = config.moduleUrl("snippets/js");
|
||||
assert.equal(url, "a/b/snippets/js.js");
|
||||
|
||||
config.setModuleUrl("snippets/js", "_.js");
|
||||
url = config.moduleUrl("snippets/js");
|
||||
assert.equal(url, "_.js");
|
||||
|
||||
url = config.moduleUrl("ace/ext/textarea");
|
||||
assert.equal(url, "a/b/ext-textarea.js");
|
||||
|
||||
assert.equal();
|
||||
},
|
||||
"test: define options" : function() {
|
||||
var o = {};
|
||||
config.defineOptions(o, "test_object", {
|
||||
opt1: {
|
||||
set: function(val) {
|
||||
this.x = val;
|
||||
},
|
||||
value: 7
|
||||
},
|
||||
initialValue: {
|
||||
set: function(val) {
|
||||
this.x = val;
|
||||
},
|
||||
initialValue: 8
|
||||
},
|
||||
opt2: {
|
||||
get: function(val) {
|
||||
return this.x;
|
||||
}
|
||||
},
|
||||
forwarded: "model"
|
||||
});
|
||||
o.model = {};
|
||||
config.defineOptions(o.model, "model", {
|
||||
forwarded: {value: 1}
|
||||
});
|
||||
|
||||
config.resetOptions(o);
|
||||
config.resetOptions(o.model);
|
||||
assert.equal(o.getOption("opt1"), 7);
|
||||
assert.equal(o.getOption("opt2"), 7);
|
||||
o.setOption("opt1", 8);
|
||||
assert.equal(o.getOption("opt1"), 8);
|
||||
assert.equal(o.getOption("opt2"), 8);
|
||||
|
||||
assert.equal(o.getOption("forwarded"), 1);
|
||||
|
||||
assert.equal(o.getOption("new"), undefined);
|
||||
o.setOption("new", 0);
|
||||
assert.equal(o.getOption("new"), undefined);
|
||||
|
||||
|
||||
assert.equal(o.getOption("initialValue"), 8);
|
||||
o.setOption("initialValue", 7);
|
||||
assert.equal(o.getOption("opt2"), 7);
|
||||
|
||||
config.setDefaultValues("test_object", {
|
||||
opt1: 1,
|
||||
forwarded: 2
|
||||
});
|
||||
config.resetOptions(o);
|
||||
assert.equal(o.getOption("opt1"), 1);
|
||||
assert.equal(o.getOption("forwarded"), 2);
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
if (typeof module !== "undefined" && module === require.main) {
|
||||
require("asyncjs").test.testcase(module.exports).exec();
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 759 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user