diff --git a/cyynote-backend/.idea/.gitignore b/cyynote-backend/.idea/.gitignore new file mode 100644 index 0000000..eaf91e2 --- /dev/null +++ b/cyynote-backend/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/cyynote-backend/.idea/codeStyles/Project.xml b/cyynote-backend/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..c2621b8 --- /dev/null +++ b/cyynote-backend/.idea/codeStyles/Project.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/cyynote-backend/.idea/codeStyles/codeStyleConfig.xml b/cyynote-backend/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..df5f35d --- /dev/null +++ b/cyynote-backend/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/cyynote-backend/.idea/compiler.xml b/cyynote-backend/.idea/compiler.xml new file mode 100644 index 0000000..adcb8c0 --- /dev/null +++ b/cyynote-backend/.idea/compiler.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cyynote-backend/.idea/copilot.data.migration.agent.xml b/cyynote-backend/.idea/copilot.data.migration.agent.xml new file mode 100644 index 0000000..2c0ecc2 --- /dev/null +++ b/cyynote-backend/.idea/copilot.data.migration.agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/cyynote-backend/.idea/copilot.data.migration.edit.xml b/cyynote-backend/.idea/copilot.data.migration.edit.xml new file mode 100644 index 0000000..bd4a159 --- /dev/null +++ b/cyynote-backend/.idea/copilot.data.migration.edit.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/cyynote-backend/.idea/encodings.xml b/cyynote-backend/.idea/encodings.xml new file mode 100644 index 0000000..a156f52 --- /dev/null +++ b/cyynote-backend/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/cyynote-backend/.idea/jarRepositories.xml b/cyynote-backend/.idea/jarRepositories.xml new file mode 100644 index 0000000..bea0df0 --- /dev/null +++ b/cyynote-backend/.idea/jarRepositories.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cyynote-backend/.idea/misc.xml b/cyynote-backend/.idea/misc.xml new file mode 100644 index 0000000..8858082 --- /dev/null +++ b/cyynote-backend/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/cyynote-backend/.idea/uiDesigner.xml b/cyynote-backend/.idea/uiDesigner.xml new file mode 100644 index 0000000..6d50cd4 --- /dev/null +++ b/cyynote-backend/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cyynote-backend/pom.xml b/cyynote-backend/pom.xml new file mode 100644 index 0000000..d2e8d55 --- /dev/null +++ b/cyynote-backend/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + com.cyynote + cyynote + 1.0.0 + jar + + ${project.artifactId} + cyy note + + + org.noear + solon-parent + 3.0.5 + + + + + 11 + + + + + org.noear + solon-web + + + + org.noear + solon-sessionstate-jwt + + + + + org.noear + wood-solon-plugin + + + + org.noear + solon-security-auth + + + + org.projectlombok + lombok + provided + + + + cn.hutool + hutool-all + 5.8.21 + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.20 + + + + com.zaxxer + HikariCP + 4.0.3 + + + + org.xerial + sqlite-jdbc + 3.46.1.3 + + + + + org.projectlombok + lombok + + provided + + + + org.noear + solon-test + test + + + + + + + ${project.artifactId} + + + + org.noear + solon-maven-plugin + + + + + + \ No newline at end of file diff --git a/cyynote-backend/src/main/java/com/cyynote/Config.java b/cyynote-backend/src/main/java/com/cyynote/Config.java new file mode 100644 index 0000000..1eafaf4 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/Config.java @@ -0,0 +1,30 @@ +package com.cyynote; + +import com.cyynote.dso.AuthProcessorImpl; +import com.cyynote.dso.DsHelper; +import com.zaxxer.hikari.HikariDataSource; +import javax.sql.DataSource; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; +import org.noear.solon.auth.AuthProcessor; +import org.noear.solon.auth.AuthUtil; +import org.noear.wood.cache.ICacheServiceEx; +import org.noear.wood.cache.LocalCache; + +@Configuration +public class Config { + public static final ICacheServiceEx cache = (new LocalCache("test", 60)).nameSet("test"); + + @Bean(value = "db1", typed = true) + public DataSource db1(@Inject("${test.db1}") HikariDataSource dataSource) { + DsHelper.initData((DataSource)dataSource); + return (DataSource)dataSource; + } + + @Bean + public void authAdapter() { + AuthUtil.adapter() + .processor((AuthProcessor)new AuthProcessorImpl()); + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/WebApp.java b/cyynote-backend/src/main/java/com/cyynote/WebApp.java new file mode 100644 index 0000000..bd915e3 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/WebApp.java @@ -0,0 +1,12 @@ +package com.cyynote; + + +import org.noear.solon.Solon; +import org.noear.wood.WoodConfig; + +public class WebApp { + public static void main(String[] args) { + Solon.start(com.cyynote.WebApp.class, args); + WoodConfig.onExecuteAft(cmd -> System.out.println("[Wood]" + cmd.text + "\r\n" + cmd.paramMap())); + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/controller/AuthController.java b/cyynote-backend/src/main/java/com/cyynote/controller/AuthController.java new file mode 100644 index 0000000..baba29e --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/controller/AuthController.java @@ -0,0 +1,103 @@ +package com.cyynote.controller; + + +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson2.JSONObject; +import com.cyynote.controller.BaseController; +import com.cyynote.model.NoteModel; +import com.cyynote.model.UserModel; +import com.cyynote.payload.request.LoginRequest; +import com.cyynote.payload.request.SignupRequest; +import java.sql.SQLException; +import java.util.List; +import java.util.logging.Logger; + +import com.cyynote.payload.request.UpdatePasswordRequest; +import org.noear.solon.annotation.Body; +import org.noear.solon.annotation.Controller; +import org.noear.solon.annotation.Mapping; +import org.noear.solon.annotation.Post; +import org.noear.solon.core.handle.Context; +import org.noear.solon.core.handle.Result; +import org.noear.wood.DbContext; +import org.noear.wood.DbTableQuery; +import org.noear.wood.annotation.Db; + +@Mapping("/api/auth") +@Controller +public class AuthController extends BaseController { + private static final Logger log = Logger.getLogger(com.cyynote.controller.AuthController.class.getName()); + + @Db + DbContext db; + + @Mapping("/logout") + public void logout(Context ctx) { + ctx.sessionClear(); + } + + @Post + @Mapping("/signin") + public Result signin(Context ctx, @Body LoginRequest loginRequest) throws SQLException { + log.info("name:" + loginRequest.getUsername()); + log.info("password:" + loginRequest.getPassword()); + + long count = db.table("user").selectCount(); + log.info("countUser:" + count); + List all = ((DbTableQuery)this.db.table("user")).selectList("*", UserModel.class); + log.info( all.toString()); + + UserModel model = (UserModel)((DbTableQuery)((DbTableQuery)this.db.table("user").whereEq("username", loginRequest.getUsername())).andEq("password", loginRequest.getPassword())).selectItem("*", UserModel.class); + if ("admin".equals(model.getUsername())) { + ctx.sessionSet("user_name", loginRequest.getUsername()); + ctx.sessionSet("user_id", model.getId()); + JSONObject obj = new JSONObject(); + String token = ctx.sessionState().sessionToken(); + obj.put("accessToken", token); + obj.put("id", Integer.valueOf(1)); + obj.put("username", loginRequest.getUsername()); + obj.put("email", "1"); + obj.put("roles", "1"); + int flag = ((DbTableQuery)this.db.table("user").set("token", token).set("logintime", DateUtil.now()).whereEq("id", model.getId())).update(); + System.out.println(flag); + return Result.succeed(obj); + } + return Result.failure(); + } + + + @Post + @Mapping("/updatePassword") + public Result updatePassword(Context ctx, @Body UpdatePasswordRequest loginRequest) throws SQLException { + log.info("name:" + loginRequest.getUsername()); + log.info("old - password:" + loginRequest.getOldpassword()); + log.info("new - password:" + loginRequest.getNewpassword()); + + + long count = db.table("user").selectCount(); + log.info("countUser:" + count); + List all = ((DbTableQuery)this.db.table("user")).selectList("*", UserModel.class); + log.info( all.toString()); + + + UserModel model = (UserModel)((DbTableQuery)((DbTableQuery)this.db.table("user").whereEq("username", loginRequest.getUsername())).andEq("password", loginRequest.getOldpassword())).selectItem("*", UserModel.class); + if ("admin".equals(model.getUsername())) { + + int flag = ((DbTableQuery)this.db.table("user").set("password", loginRequest.getNewpassword()).whereEq("id", model.getId())).update(); + System.out.println(flag); + return Result.succeed(flag); + } + return Result.failure(); + } + + + + @Post + @Mapping("/signup") + public Result signup(@Body SignupRequest signUpRequest) { + log.info("signUpRequest:" + JSONObject.toJSONString(signUpRequest, new com.alibaba.fastjson2.JSONWriter.Feature[0])); + JSONObject obj = new JSONObject(); + return Result.succeed(obj); + } +} + diff --git a/cyynote-backend/src/main/java/com/cyynote/controller/BaseController.java b/cyynote-backend/src/main/java/com/cyynote/controller/BaseController.java new file mode 100644 index 0000000..8fd5620 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/controller/BaseController.java @@ -0,0 +1,6 @@ +package com.cyynote.controller; + +import org.noear.solon.web.cors.annotation.CrossOrigin; + +@CrossOrigin(origins = "*") +public class BaseController {} diff --git a/cyynote-backend/src/main/java/com/cyynote/controller/DemoController.java b/cyynote-backend/src/main/java/com/cyynote/controller/DemoController.java new file mode 100644 index 0000000..5bd8686 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/controller/DemoController.java @@ -0,0 +1,55 @@ +package com.cyynote.controller; + + +import com.cyynote.controller.BaseController; +import org.noear.solon.annotation.Controller; +import org.noear.solon.annotation.Delete; +import org.noear.solon.annotation.Get; +import org.noear.solon.annotation.Mapping; +import org.noear.solon.annotation.Patch; +import org.noear.solon.annotation.Post; +import org.noear.solon.annotation.Put; +import org.noear.solon.core.handle.Context; +import org.noear.solon.core.handle.ModelAndView; + +@Controller +public class DemoController extends BaseController { + @Mapping("/demo") + public Object test() { + ModelAndView model = new ModelAndView("beetl.htm"); + model.put("title", "dock"); + model.put("message", "world!"); + return model; + } + + @Post + @Mapping("post") + public String test_post(Context context) { + return context.param("name"); + } + + @Put + @Mapping("put") + public String test_put(Context context, String name) { + return context.param("name"); + } + + @Delete + @Mapping("delete") + public String test_delete(Context context, String name) { + return context.param("name"); + } + + @Patch + @Mapping("patch") + public String test_patch(Context context, String name) { + return context.param("name"); + } + + @Get + @Mapping("get") + public String test_post_get(Context context) { + return context.path(); + } +} + diff --git a/cyynote-backend/src/main/java/com/cyynote/controller/JwtInterceptor.java b/cyynote-backend/src/main/java/com/cyynote/controller/JwtInterceptor.java new file mode 100644 index 0000000..e9cc563 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/controller/JwtInterceptor.java @@ -0,0 +1,44 @@ +package com.cyynote.controller; + + +import io.jsonwebtoken.Claims; +import org.noear.solon.annotation.Component; +import org.noear.solon.core.handle.Context; +import org.noear.solon.core.handle.Handler; +import org.noear.solon.core.handle.Result; +import org.noear.solon.core.route.RouterInterceptor; +import org.noear.solon.core.route.RouterInterceptorChain; +import org.noear.solon.sessionstate.jwt.JwtUtils; + +@Component +public class JwtInterceptor implements RouterInterceptor { + public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable { + boolean flag = true; + if ("/api/auth/signup".equals(ctx.path()) || + "/api/auth/signin".equals(ctx.path()) || + "/demo".equals(ctx.path()) || + "/api/auth/logout".equals(ctx.path()) + + ) + flag = false; + if (flag) + if (null != ctx.header("Authorization")) { + String token = ctx.header("Authorization"); + token = token.replace("Bearer ", ""); + System.out.println("token:" + token); + Claims claims = JwtUtils.parseJwt(token); + System.out.println("claims:" + claims); + if (null != claims) { + System.out.println("username:" + claims.get("user_name")); + System.out.println("exp:" + claims.get("exp")); + } else { + ctx.status(401); + ctx.render(Result.failure(401, "")); + } + } else { + ctx.status(401); + ctx.render(Result.failure(401, "")); + } + chain.doIntercept(ctx, mainHandler); + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/controller/NoteController.java b/cyynote-backend/src/main/java/com/cyynote/controller/NoteController.java new file mode 100644 index 0000000..50e8284 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/controller/NoteController.java @@ -0,0 +1,207 @@ +package com.cyynote.controller; + +import cn.hutool.*; +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONObject; +import com.cyynote.controller.BaseController; +import com.cyynote.dso.NoteSqlAnnotation; +import com.cyynote.model.HistoryModel; +import com.cyynote.model.NoteModel; +import com.cyynote.payload.request.NoteRequest; +import com.cyynote.util.TreeBuild; +import com.cyynote.util.TreeNode; +import io.jsonwebtoken.Claims; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.logging.Logger; +import org.noear.solon.annotation.Body; +import org.noear.solon.annotation.Controller; +import org.noear.solon.annotation.Get; +import org.noear.solon.annotation.Mapping; +import org.noear.solon.annotation.Path; +import org.noear.solon.annotation.Post; +import org.noear.solon.annotation.Put; +import org.noear.solon.core.handle.Context; +import org.noear.solon.core.handle.Result; +import org.noear.solon.sessionstate.jwt.JwtUtils; +import org.noear.solon.web.cors.annotation.CrossOrigin; +import org.noear.wood.DbContext; +import org.noear.wood.DbTableQuery; +import org.noear.wood.annotation.Db; + +@Mapping("/api/note") +@Controller +public class NoteController extends BaseController { + private static final Logger log = Logger.getLogger(com.cyynote.controller.NoteController.class.getName()); + + @Db + NoteSqlAnnotation mapper; + + @Db + DbContext db; + + private static final String QUERY_F = "id,pid,title,createtime,updatetime,viewtime,flag"; + + @Get + @Mapping("get1") + public String test_post_get(Context context, @Path String appname) { + + return context.path(); + } + + @Post + @Mapping("post") + public String test_post(Context context) { + return context.param("name"); + } + + @Put + @Mapping("put") + public String test_put(Context context, String name) { + return context.param("name"); + } + + @Get + @Mapping("/all") + public Result noteAccess(Context ctx) throws Exception { + List all = ((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + List treeNodeList = new ArrayList<>(); + for (NoteModel n : all) { + TreeNode treeNode = new TreeNode(n.getId().intValue(), n.getPid().intValue(), n.getTitle(), String.valueOf(n.getId()), String.valueOf(n.getId())); + treeNodeList.add(treeNode); + } + TreeBuild bb = new TreeBuild(treeNodeList); + List collect = bb.buildTree(); + return Result.succeed(JSONArray.parseArray(JSONArray.toJSONString(collect))); + } + + @Get + @Mapping("/home") + public Result noteHome(Context ctx, @Path int type, @Path String search) throws Exception { + System.out.println(type); + System.out.println(search); + List all = new ArrayList<>(); + if (type == 0) { + all = ((DbTableQuery)((DbTableQuery)this.db.table("note").whereLk("context", "%" + search + "%")).andLk("title", "%" + search + "%")).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + } else if (type == 1) { + all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).orderByDesc("updatetime")).limit(20)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + } else if (type == 2) { + all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).orderByDesc("createtime")).limit(20)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + } else if (type == 3) { + all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).orderByDesc("viewtime")).limit(20)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + } else if (type == 4) { + all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(0))).orderByDesc("updatetime")).limit(10000)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + } + return Result.succeed(JSONArray.parseArray(JSONArray.toJSONString(all))); + } + + @Get + @Mapping("/get") + public Result getNote(Context ctx, @Path int id) throws Exception { + NoteModel model = (NoteModel)((DbTableQuery)this.db.table("note").whereEq("id", Integer.valueOf(id))).selectItem("*", NoteModel.class); + String time = DateUtil.now(); + System.out.println(time); + int flag = ((DbTableQuery)this.db.table("note").set("viewtime", time).whereEq("id", Integer.valueOf(id))).update(); + System.out.println(flag); + log.info(JSONObject.toJSONString(model, new com.alibaba.fastjson2.JSONWriter.Feature[0])); + return Result.succeed(model); + } + + @Get + @Mapping("/add") + public Result addNote(Context ctx, @Path int pid) throws Exception { + String userid = getUserInfoId(ctx); + if (null == userid) + return Result.failure(401); + NoteModel note = new NoteModel(); + note.setPid(Integer.valueOf(pid)); + note.setTitle("未命名Note"); + note.setFlag(Integer.valueOf(1)); + note.setCreatetime(DateUtil.now()); + note.setContext("{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"未命名Note\",\"type\":\"text\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"paragraph\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}"); + +// note.setContext("{\"root\": {\"type\": \"root\", \"format\": \"\", \"indent\": 0, \"version\": 1, \"children\": [{\"type\": \"paragraph\", \"format\": \"\", \"indent\": 0, \"version\": 1, \"children\": [], \"direction\": null}], \"direction\": null}}"); + this.db.table("note").setEntityIf(note, (k, v) -> Boolean.valueOf((v != null))).insert(); + return Result.succeed("ok"); + } + + @Post + @Mapping("/update") + public Result updateNote(@Body NoteRequest noteRequest) throws SQLException { + NoteModel note = (NoteModel)((DbTableQuery)this.db.table("note").whereEq("id", noteRequest.getId())).selectItem("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + note.setTitle(noteRequest.getTitle()); + note.setUpdatetime(DateUtil.now()); + String cc = noteRequest.getContext(); + JSONObject ar = JSONObject.parseObject(cc); + note.setContext(ar.toJSONString()); + int flag = ((DbTableQuery)this.db.table("note").set("title", noteRequest.getTitle()).set("updatetime", DateUtil.now()).set("context", ar.toJSONString()).whereEq("id", noteRequest.getId())).update(); + log.info("updateNote+ flag"); + HistoryModel history = new HistoryModel(); + history.setNid(Integer.valueOf(noteRequest.getId().intValue())); + history.setTitle(noteRequest.getTitle()); + history.setFlag(Integer.valueOf(1)); + history.setCreatetime(DateUtil.now()); + history.setContext(ar.toJSONString()); + long his = this.db.table("history").setEntityIf(history, (k, v) -> Boolean.valueOf((v != null))).insert(); + log.info("history+ his"); + return Result.succeed("ok"); + } + + @Post + @Mapping("/delete") + public Result deleteNote(@Body NoteRequest noteRequest) throws SQLException { + int flag = ((DbTableQuery)this.db.table("note").set("title", noteRequest.getTitle()).set("flag", Integer.valueOf(0)).whereEq("id", noteRequest.getId())).update(); + System.out.println(flag); + return Result.succeed("ok"); + } + + @Get + @Mapping("/deleteBack") + public Result deleteBack(@Path int id) throws Exception { + int flag = ((DbTableQuery)this.db.table("note").set("flag", Integer.valueOf(1)).whereEq("id", Integer.valueOf(id))).update(); + System.out.println(flag); + return Result.succeed("ok"); + } + + @Get + @Mapping("/historyQueryOrDelete") + public Result historyQueryOrDelete(@Path int flag) throws Exception { + + log.info("history+ flag" + flag); + + if(flag==0){ + long count = db.table("history").selectCount(); + log.info("count:" + count); + return Result.succeed(count); + }else if(flag==1){ + int flagdb = db.table("history").whereEq("flag",1).delete(); + log.info("flagdb:" + flagdb); + return Result.succeed(0); + } + return null; + } + + private String getUserInfoId(Context ctx) { + String userId = ""; + if (null != ctx.header("Authorization")) { + String token = ctx.header("Authorization"); + token = token.replace("Bearer ", ""); + System.out.println("token:" + token); + Claims claims = JwtUtils.parseJwt(token); + System.out.println("claims:" + claims); + if (null != claims) { + System.out.println("username:" + claims.get("user_name")); + System.out.println("userid:" + claims.get("user_id")); + System.out.println("exp:" + claims.get("exp")); + userId = claims.get("user_id").toString(); + return userId; + } + return null; + } + return null; + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/controller/Test2Controller.java b/cyynote-backend/src/main/java/com/cyynote/controller/Test2Controller.java new file mode 100644 index 0000000..61e977b --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/controller/Test2Controller.java @@ -0,0 +1,34 @@ +package com.cyynote.controller; + +import com.cyynote.controller.BaseController; +import org.noear.solon.annotation.Controller; +import org.noear.solon.annotation.Get; +import org.noear.solon.annotation.Mapping; + +@Controller +@Mapping("/api/test") +public class Test2Controller extends BaseController { + @Get + @Mapping("/all") + public String allAccess() { + return "Public Content."; + } + + @Get + @Mapping("/user") + public String userAccess() { + return "User Content."; + } + + @Get + @Mapping("/mod") + public String moderatorAccess() { + return "Moderator Board."; + } + + @Get + @Mapping("/admin") + public String adminAccess() { + return "Admin Board."; + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/dso/AuthProcessorImpl.java b/cyynote-backend/src/main/java/com/cyynote/dso/AuthProcessorImpl.java new file mode 100644 index 0000000..466cab3 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/dso/AuthProcessorImpl.java @@ -0,0 +1,26 @@ +package com.cyynote.dso; + + +import java.util.ArrayList; +import java.util.List; +import org.noear.solon.auth.AuthProcessorBase; + +public class AuthProcessorImpl extends AuthProcessorBase { + public boolean verifyLogined() { + return true; + } + + protected List getPermissions() { + List list = new ArrayList<>(); + list.add("user:add"); + list.add("user:demo"); + return list; + } + + protected List getRoles() { + List list = new ArrayList<>(); + list.add("admin1"); + list.add("admin2"); + return list; + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/dso/AuthSqlAnnotation.java b/cyynote-backend/src/main/java/com/cyynote/dso/AuthSqlAnnotation.java new file mode 100644 index 0000000..e618952 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/dso/AuthSqlAnnotation.java @@ -0,0 +1,19 @@ +package com.cyynote.dso; + +import com.cyynote.model.UserModel; +import java.util.Map; +import org.noear.wood.BaseMapper; +import org.noear.wood.annotation.Db; +import org.noear.wood.annotation.Sql; + +@Db +public interface AuthSqlAnnotation extends BaseMapper { + @Sql("select app_id from appx limit 1") + int user_get() throws Exception; + + @Sql(value = "select * from user where id = @{id} limit 1", caching = "test", cacheTag = "app_${id}") + UserModel user_get2(int paramInt) throws Exception; + + @Sql(value = "select * from ${tb} where id = @{id} limit 1", cacheClear = "test") + Map user_get3(String paramString, int paramInt) throws Exception; +} diff --git a/cyynote-backend/src/main/java/com/cyynote/dso/DsHelper.java b/cyynote-backend/src/main/java/com/cyynote/dso/DsHelper.java new file mode 100644 index 0000000..23a4dda --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/dso/DsHelper.java @@ -0,0 +1,59 @@ +package com.cyynote.dso; + + +import java.io.InputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import javax.sql.DataSource; + +public class DsHelper { + private static boolean inited = false; + + public static void initData(DataSource ds) { + if (inited) + return; + inited = true; + Connection conn = null; + try { + conn = ds.getConnection(); + PreparedStatement ps = conn.prepareStatement("SELECT name FROM sqlite_master WHERE type='table' AND name='appx';"); + ResultSet res = ps.executeQuery(); + if (!res.next()) { + String[] sqls = getSqlFromFile(); + for (String sql : sqls) + runSql(conn, sql); + } + ps.close(); + } catch (SQLException sqlException) { + throw new RuntimeException(sqlException); + } finally { + try { + if (conn != null) + conn.close(); + } catch (SQLException sQLException) {} + } + } + + private static String[] getSqlFromFile() { + try { + InputStream ins = com.cyynote.dso.DsHelper.class.getResourceAsStream("/db/schema.sql"); + int len = ins.available(); + byte[] bs = new byte[len]; + ins.read(bs); + String str = new String(bs, "UTF-8"); + String[] sql = str.split(";"); + return sql; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private static void runSql(Connection conn, String sql) throws SQLException { + System.out.println(sql); + PreparedStatement ps = conn.prepareStatement(sql); + ps.executeUpdate(); + ps.close(); + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/dso/NoteSqlAnnotation.java b/cyynote-backend/src/main/java/com/cyynote/dso/NoteSqlAnnotation.java new file mode 100644 index 0000000..2667f4b --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/dso/NoteSqlAnnotation.java @@ -0,0 +1,32 @@ +package com.cyynote.dso; + +import com.cyynote.model.NoteModel; +import java.util.List; +import java.util.Map; +import org.noear.wood.BaseMapper; +import org.noear.wood.annotation.Db; +import org.noear.wood.annotation.Sql; + +@Db +public interface NoteSqlAnnotation extends BaseMapper { + @Sql("select id from note limit 1") + int note_get() throws Exception; + + @Sql("select * from note where flag =1 limit 10000") + List findAll() throws Exception; + + @Sql(value = "select * from note where id = @{id} limit 1", caching = "test", cacheTag = "app_${id}") + NoteModel note_get2(int paramInt) throws Exception; + + @Sql(value = "select * from ${tb} where id = @{id} limit 1", cacheClear = "test") + Map note_get3(String paramString, int paramInt) throws Exception; + + @Sql("insert into note (`pid`,`title`,`context`,`createtime`,`flag`) values (@{pid},${title},${context},${createtime},@{flag})") + Map insert(int paramInt1, String paramString1, String paramString2, String paramString3, int paramInt2) throws Exception; + + @Sql("update note set id = @{id} where id = @{id} ") + Map update(String paramString, int paramInt) throws Exception; + + @Sql("update note set viewtime = @{viewtime} where id = @{id}") + Map updateViewtime(String paramString, int paramInt) throws Exception; +} diff --git a/cyynote-backend/src/main/java/com/cyynote/dso/SqlAnnotation.java b/cyynote-backend/src/main/java/com/cyynote/dso/SqlAnnotation.java new file mode 100644 index 0000000..f29205c --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/dso/SqlAnnotation.java @@ -0,0 +1,26 @@ +package com.cyynote.dso; + +import com.cyynote.model.AppxModel; +import java.util.List; +import java.util.Map; +import org.noear.wood.BaseMapper; +import org.noear.wood.annotation.Db; +import org.noear.wood.annotation.Sql; + +@Db +public interface SqlAnnotation extends BaseMapper { + @Sql("select app_id from appx limit 1") + int appx_get() throws Exception; + + @Sql(value = "select * from appx where app_id = @{app_id} limit 1", caching = "test", cacheTag = "app_${app_id}") + AppxModel appx_get2(int paramInt) throws Exception; + + @Sql(value = "select * from ${tb} where app_id = @{app_id} limit 1", cacheClear = "test") + Map appx_get3(String paramString, int paramInt) throws Exception; + + @Sql("select * from appx where app_id>@{app_id} order by app_id asc limit 4") + List appx_getlist(int paramInt) throws Exception; + + @Sql("select app_id from appx limit 4") + List appx_getids() throws Exception; +} diff --git a/cyynote-backend/src/main/java/com/cyynote/dso/SqlMapper.java b/cyynote-backend/src/main/java/com/cyynote/dso/SqlMapper.java new file mode 100644 index 0000000..981883d --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/dso/SqlMapper.java @@ -0,0 +1,20 @@ +package com.cyynote.dso; + +import com.cyynote.model.AppxModel; +import java.util.List; +import java.util.Map; +import org.noear.wood.BaseMapper; +import org.noear.wood.xml.Namespace; + +@Namespace("webapp.dso") +public interface SqlMapper extends BaseMapper { + int appx_get() throws Exception; + + AppxModel appx_get2(int paramInt) throws Exception; + + Map appx_get3(String paramString, int paramInt) throws Exception; + + List appx_getlist(int paramInt) throws Exception; + + List appx_getids() throws Exception; +} diff --git a/cyynote-backend/src/main/java/com/cyynote/model/Appx.java b/cyynote-backend/src/main/java/com/cyynote/model/Appx.java new file mode 100644 index 0000000..0664f25 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/model/Appx.java @@ -0,0 +1,17 @@ +package com.cyynote.model; + + +import org.noear.wood.annotation.PrimaryKey; + +public class Appx { + @PrimaryKey + public int app_id; + + public int agroup_id; + + public String note; + + public String app_key; + + public int ar_is_examine; +} diff --git a/cyynote-backend/src/main/java/com/cyynote/model/AppxModel.java b/cyynote-backend/src/main/java/com/cyynote/model/AppxModel.java new file mode 100644 index 0000000..ebb3eb7 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/model/AppxModel.java @@ -0,0 +1,20 @@ +package com.cyynote.model; + + +import lombok.Getter; +import lombok.Setter; +import org.noear.wood.annotation.PrimaryKey; +import org.noear.wood.annotation.Table; + +@Getter +@Setter +@Table("appx") +public class AppxModel { + @PrimaryKey + private int app_id; + private int agroup_id; + private String note; + private String app_key; + private int ar_is_examine; +} + diff --git a/cyynote-backend/src/main/java/com/cyynote/model/HistoryModel.java b/cyynote-backend/src/main/java/com/cyynote/model/HistoryModel.java new file mode 100644 index 0000000..1c0d00f --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/model/HistoryModel.java @@ -0,0 +1,86 @@ +package com.cyynote.model; + + +import lombok.Data; +import org.noear.wood.annotation.PrimaryKey; +import org.noear.wood.annotation.Table; + +@Data +@Table("history") +public class HistoryModel { + @PrimaryKey + public Integer id; + + public Integer nid; + + public String title; + + public String context; + + public String conjson; + + public String createtime; + + public Integer flag; + + public void setId(Integer id) { + this.id = id; + } + + public void setNid(Integer nid) { + this.nid = nid; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setContext(String context) { + this.context = context; + } + + public void setConjson(String conjson) { + this.conjson = conjson; + } + + public void setCreatetime(String createtime) { + this.createtime = createtime; + } + + public void setFlag(Integer flag) { + this.flag = flag; + } + + + public String toString() { + return "HistoryModel(id=" + getId() + ", nid=" + getNid() + ", title=" + getTitle() + ", context=" + getContext() + ", conjson=" + getConjson() + ", createtime=" + getCreatetime() + ", flag=" + getFlag() + ")"; + } + + public Integer getId() { + return this.id; + } + + public Integer getNid() { + return this.nid; + } + + public String getTitle() { + return this.title; + } + + public String getContext() { + return this.context; + } + + public String getConjson() { + return this.conjson; + } + + public String getCreatetime() { + return this.createtime; + } + + public Integer getFlag() { + return this.flag; + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/model/NoteModel.java b/cyynote-backend/src/main/java/com/cyynote/model/NoteModel.java new file mode 100644 index 0000000..6dda42d --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/model/NoteModel.java @@ -0,0 +1,107 @@ +package com.cyynote.model; + + +import lombok.Data; +import org.noear.wood.annotation.PrimaryKey; +import org.noear.wood.annotation.Table; + +@Data +@Table("note") +public class NoteModel { + @PrimaryKey + public Integer id; + + public Integer pid; + + public String title; + + public String context; + + public String conjson; + + public String createtime; + + public String updatetime; + + public String viewtime; + + public Integer flag; + + public void setId(Integer id) { + this.id = id; + } + + public void setPid(Integer pid) { + this.pid = pid; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setContext(String context) { + this.context = context; + } + + public void setConjson(String conjson) { + this.conjson = conjson; + } + + public void setCreatetime(String createtime) { + this.createtime = createtime; + } + + public void setUpdatetime(String updatetime) { + this.updatetime = updatetime; + } + + public void setViewtime(String viewtime) { + this.viewtime = viewtime; + } + + public void setFlag(Integer flag) { + this.flag = flag; + } + + + public String toString() { + return "NoteModel(id=" + getId() + ", pid=" + getPid() + ", title=" + getTitle() + ", context=" + getContext() + ", conjson=" + getConjson() + ", createtime=" + getCreatetime() + ", updatetime=" + getUpdatetime() + ", viewtime=" + getViewtime() + ", flag=" + getFlag() + ")"; + } + + public Integer getId() { + return this.id; + } + + public Integer getPid() { + return this.pid; + } + + public String getTitle() { + return this.title; + } + + public String getContext() { + return this.context; + } + + public String getConjson() { + return this.conjson; + } + + public String getCreatetime() { + return this.createtime; + } + + public String getUpdatetime() { + return this.updatetime; + } + + public String getViewtime() { + return this.viewtime; + } + + public Integer getFlag() { + return this.flag; + } +} + diff --git a/cyynote-backend/src/main/java/com/cyynote/model/UserModel.java b/cyynote-backend/src/main/java/com/cyynote/model/UserModel.java new file mode 100644 index 0000000..c14197b --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/model/UserModel.java @@ -0,0 +1,85 @@ +package com.cyynote.model; + + +import lombok.Data; +import org.noear.wood.annotation.PrimaryKey; +import org.noear.wood.annotation.Table; + +@Data +@Table("user") +public class UserModel { + @PrimaryKey + public Integer id; + + public String email; + + public String password; + + public String username; + + public String role; + + public String token; + + public String logintime; + + public void setId(Integer id) { + this.id = id; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setRole(String role) { + this.role = role; + } + + public void setToken(String token) { + this.token = token; + } + + public void setLogintime(String logintime) { + this.logintime = logintime; + } + + public String toString() { + return "UserModel(id=" + getId() + ", email=" + getEmail() + ", password=" + getPassword() + ", username=" + getUsername() + ", role=" + getRole() + ", token=" + getToken() + ", logintime=" + getLogintime() + ")"; + } + + public Integer getId() { + return this.id; + } + + public String getEmail() { + return this.email; + } + + public String getPassword() { + return this.password; + } + + public String getUsername() { + return this.username; + } + + public String getRole() { + return this.role; + } + + public String getToken() { + return this.token; + } + + public String getLogintime() { + return this.logintime; + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/payload/request/LoginRequest.java b/cyynote-backend/src/main/java/com/cyynote/payload/request/LoginRequest.java new file mode 100644 index 0000000..8408463 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/payload/request/LoginRequest.java @@ -0,0 +1,25 @@ +package com.cyynote.payload.request; + + +public class LoginRequest { + private String username; + + private String password; + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } +} + diff --git a/cyynote-backend/src/main/java/com/cyynote/payload/request/NoteRequest.java b/cyynote-backend/src/main/java/com/cyynote/payload/request/NoteRequest.java new file mode 100644 index 0000000..511889b --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/payload/request/NoteRequest.java @@ -0,0 +1,44 @@ +package com.cyynote.payload.request; + + +public class NoteRequest { + private Long id; + + private Long pid; + + private String title; + + private String context; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getPid() { + return this.pid; + } + + public void setPid(Long pid) { + this.pid = pid; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContext() { + return this.context; + } + + public void setContext(String context) { + this.context = context; + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/payload/request/SignupRequest.java b/cyynote-backend/src/main/java/com/cyynote/payload/request/SignupRequest.java new file mode 100644 index 0000000..3914769 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/payload/request/SignupRequest.java @@ -0,0 +1,47 @@ +package com.cyynote.payload.request; + + +import java.util.Set; + +public class SignupRequest { + private String username; + + private String email; + + private Set role; + + private String password; + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return this.email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getRole() { + return this.role; + } + + public void setRole(Set role) { + this.role = role; + } +} + diff --git a/cyynote-backend/src/main/java/com/cyynote/payload/request/UpdatePasswordRequest.java b/cyynote-backend/src/main/java/com/cyynote/payload/request/UpdatePasswordRequest.java new file mode 100644 index 0000000..04f1e58 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/payload/request/UpdatePasswordRequest.java @@ -0,0 +1,17 @@ +package com.cyynote.payload.request; + + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UpdatePasswordRequest { + private String username; + + private String oldpassword; + + private String newpassword; + +} + diff --git a/cyynote-backend/src/main/java/com/cyynote/payload/response/JwtResponse.java b/cyynote-backend/src/main/java/com/cyynote/payload/response/JwtResponse.java new file mode 100644 index 0000000..13b3bb8 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/payload/response/JwtResponse.java @@ -0,0 +1,70 @@ +package com.cyynote.payload.response; + + +import java.util.List; + +public class JwtResponse { + private String token; + + private String type = "Bearer"; + + private Long id; + + private String username; + + private String email; + + private List roles; + + public JwtResponse(String accessToken, Long id, String username, String email, List roles) { + this.token = accessToken; + this.id = id; + this.username = username; + this.email = email; + this.roles = roles; + } + + public String getAccessToken() { + return this.token; + } + + public void setAccessToken(String accessToken) { + this.token = accessToken; + } + + public String getTokenType() { + return this.type; + } + + public void setTokenType(String tokenType) { + this.type = tokenType; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEmail() { + return this.email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public List getRoles() { + return this.roles; + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/payload/response/MessageResponse.java b/cyynote-backend/src/main/java/com/cyynote/payload/response/MessageResponse.java new file mode 100644 index 0000000..cd7fdad --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/payload/response/MessageResponse.java @@ -0,0 +1,17 @@ +package com.cyynote.payload.response; + +public class MessageResponse { + private String message; + + public MessageResponse(String message) { + this.message = message; + } + + public String getMessage() { + return this.message; + } + + public void setMessage(String message) { + this.message = message; + } +} \ No newline at end of file diff --git a/cyynote-backend/src/main/java/com/cyynote/util/TreeBuild.java b/cyynote-backend/src/main/java/com/cyynote/util/TreeBuild.java new file mode 100644 index 0000000..21eadc8 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/util/TreeBuild.java @@ -0,0 +1,42 @@ +package com.cyynote.util; + + +import com.cyynote.util.TreeNode; +import java.util.ArrayList; +import java.util.List; + +public class TreeBuild { + public List nodeList = new ArrayList<>(); + + public TreeBuild(List nodeList) { + this.nodeList = nodeList; + } + + public List getRootNode() { + List rootNodeList = new ArrayList<>(); + for (TreeNode treeNode : this.nodeList) { + if (0 == treeNode.getPid()) + rootNodeList.add(treeNode); + } + return rootNodeList; + } + + public List buildTree() { + List treeNodes = new ArrayList<>(); + for (TreeNode treeRootNode : getRootNode()) { + treeRootNode = buildChildTree(treeRootNode); + treeNodes.add(treeRootNode); + } + return treeNodes; + } + + public TreeNode buildChildTree(TreeNode pNode) { + List childTree = new ArrayList<>(); + for (TreeNode treeNode : this.nodeList) { + if (treeNode.getPid() == pNode.getId()) + childTree.add(buildChildTree(treeNode)); + } + pNode.setChildren(childTree); + return pNode; + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/util/TreeNode.java b/cyynote-backend/src/main/java/com/cyynote/util/TreeNode.java new file mode 100644 index 0000000..b54fd99 --- /dev/null +++ b/cyynote-backend/src/main/java/com/cyynote/util/TreeNode.java @@ -0,0 +1,74 @@ +package com.cyynote.util; + + +import java.util.List; + +public class TreeNode { + private int id; + + private int pid; + + private String label; + + private String value; + + private String key; + + private List children; + + public TreeNode(int id, int pid, String label, String value, String key) { + this.id = id; + this.pid = pid; + this.label = label; + this.value = value; + this.key = key; + } + + public int getId() { + return this.id; + } + + public void setId(int id) { + this.id = id; + } + + public int getPid() { + return this.pid; + } + + public void setPid(int pid) { + this.pid = pid; + } + + public String getLabel() { + return this.label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getValue() { + return this.value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getKey() { + return this.key; + } + + public void setKey(String key) { + this.key = key; + } + + public List getChildren() { + return this.children; + } + + public void setChildren(List children) { + this.children = children; + } +} diff --git a/cyynote-backend/src/main/resources/app-dev.yml b/cyynote-backend/src/main/resources/app-dev.yml new file mode 100644 index 0000000..da27b44 --- /dev/null +++ b/cyynote-backend/src/main/resources/app-dev.yml @@ -0,0 +1,3 @@ +test.db1: + jdbcUrl: "jdbc:sqlite:D:/cyynote.db" + driverClassName: "org.sqlite.JDBC" \ No newline at end of file diff --git a/cyynote-backend/src/main/resources/app-pro.yml b/cyynote-backend/src/main/resources/app-pro.yml new file mode 100644 index 0000000..285460d --- /dev/null +++ b/cyynote-backend/src/main/resources/app-pro.yml @@ -0,0 +1,4 @@ + +test.db1: + jdbcUrl: "jdbc:sqlite:/opt/db/cyynote.db" + driverClassName: "org.sqlite.JDBC" \ No newline at end of file diff --git a/cyynote-backend/src/main/resources/app.yml b/cyynote-backend/src/main/resources/app.yml new file mode 100644 index 0000000..8f78758 --- /dev/null +++ b/cyynote-backend/src/main/resources/app.yml @@ -0,0 +1,23 @@ +server.port: 8080 + +solon.app: + name: demoapp + group: demo + +# 基于 header 传(适合接口开发,每次会自动输出[可改为手动]) +solon.env: dev + + +solon.output.meta: 1 + + +server.session: + timeout: 7200 #单位秒;(可不配,默认:7200) + state: + jwt: + name: TOKEN #变量名;(可不配,默认:TOKEN) + secret: "E3F9N2kRDQf55pnJPnFoo5+ylKmZQ7AXmWeOVPKbEd8=" #密钥(使用 JwtUtils.createKey() 生成);(可不配,默认:xxx) + prefix: Bearer #令牌前缀(可不配,默认:空) + allowExpire: true #充许超时;(可不配,默认:true);false,则token一直有效 + allowAutoIssue: true #充许自动输出;(可不配,默认:true);flase,则不向header 或 cookie 设置值(由用户手动控制) + allowUseHeader: true #充许使用Header传递;(可不配,默认:使用 Cookie 传递);true,则使用 header 传递 \ No newline at end of file diff --git a/cyynote-backend/src/main/resources/db/schema.sql b/cyynote-backend/src/main/resources/db/schema.sql new file mode 100644 index 0000000..5c54a78 --- /dev/null +++ b/cyynote-backend/src/main/resources/db/schema.sql @@ -0,0 +1,53 @@ + + + + +CREATE TABLE `user` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT, + password TEXT, + username TEXT, + role TEXT, + token TEXT, + logintime TEXT +); +CREATE TABLE `note` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pid INTEGER, + title TEXT, + context TEXT, + conjson TEXT, + createtime TEXT, + updatetime TEXT, + viewtime TEXT, + userid INTEGER, + username TEXT, + flag INTEGER DEFAULT 1 +); +CREATE TABLE `history` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + nid INTEGER, + title TEXT, + context TEXT, + conjson TEXT, + createtime TEXT, + userid INTEGER, + username TEXT +); +CREATE TABLE appx ( + app_id INTEGER PRIMARY KEY AUTOINCREMENT, + app_key TEXT, + akey TEXT, + ugroup_id INTEGER DEFAULT 0, + agroup_id INTEGER, + name TEXT, + note TEXT, + ar_is_setting INTEGER NOT NULL DEFAULT 0, + ar_is_examine INTEGER NOT NULL DEFAULT 0, + ar_examine_ver INTEGER NOT NULL DEFAULT 0, + log_fulltime TEXT +); + +INSERT INTO `user` VALUES ('1', 'aaaaaaaaa', 'cyy123', 'admin', 'admin','',''); +INSERT INTO `user` VALUES ('2', 'aaaaaaaaa', 'test', 'test', 'test','',''); +INSERT INTO `note` VALUES ('1', '0', 'test', 'test', 'test','','','','1','1','1'); \ No newline at end of file diff --git a/cyynote-backend/src/main/resources/wood/SqlMapper.xml b/cyynote-backend/src/main/resources/wood/SqlMapper.xml new file mode 100644 index 0000000..1a86e76 --- /dev/null +++ b/cyynote-backend/src/main/resources/wood/SqlMapper.xml @@ -0,0 +1,31 @@ + + + + select app_id from appx limit 1 + + + + + + + + select * from appx where app_id = @{app_id:int} limit 1 + + + + select * from appx where app_id > @{app_id:int} order by app_id asc limit 4 + + + + + select app_id from appx limit 4 + + + + select app_id from appx limit 4 + + + \ No newline at end of file diff --git a/cyynote-backend/target/classes/app-dev.yml b/cyynote-backend/target/classes/app-dev.yml new file mode 100644 index 0000000..da27b44 --- /dev/null +++ b/cyynote-backend/target/classes/app-dev.yml @@ -0,0 +1,3 @@ +test.db1: + jdbcUrl: "jdbc:sqlite:D:/cyynote.db" + driverClassName: "org.sqlite.JDBC" \ No newline at end of file diff --git a/cyynote-backend/target/classes/app-pro.yml b/cyynote-backend/target/classes/app-pro.yml new file mode 100644 index 0000000..285460d --- /dev/null +++ b/cyynote-backend/target/classes/app-pro.yml @@ -0,0 +1,4 @@ + +test.db1: + jdbcUrl: "jdbc:sqlite:/opt/db/cyynote.db" + driverClassName: "org.sqlite.JDBC" \ No newline at end of file diff --git a/cyynote-backend/target/classes/app.yml b/cyynote-backend/target/classes/app.yml new file mode 100644 index 0000000..8f78758 --- /dev/null +++ b/cyynote-backend/target/classes/app.yml @@ -0,0 +1,23 @@ +server.port: 8080 + +solon.app: + name: demoapp + group: demo + +# 基于 header 传(适合接口开发,每次会自动输出[可改为手动]) +solon.env: dev + + +solon.output.meta: 1 + + +server.session: + timeout: 7200 #单位秒;(可不配,默认:7200) + state: + jwt: + name: TOKEN #变量名;(可不配,默认:TOKEN) + secret: "E3F9N2kRDQf55pnJPnFoo5+ylKmZQ7AXmWeOVPKbEd8=" #密钥(使用 JwtUtils.createKey() 生成);(可不配,默认:xxx) + prefix: Bearer #令牌前缀(可不配,默认:空) + allowExpire: true #充许超时;(可不配,默认:true);false,则token一直有效 + allowAutoIssue: true #充许自动输出;(可不配,默认:true);flase,则不向header 或 cookie 设置值(由用户手动控制) + allowUseHeader: true #充许使用Header传递;(可不配,默认:使用 Cookie 传递);true,则使用 header 传递 \ No newline at end of file diff --git a/cyynote-backend/target/classes/com/cyynote/Config.class b/cyynote-backend/target/classes/com/cyynote/Config.class new file mode 100644 index 0000000..c0521bd Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/Config.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/WebApp.class b/cyynote-backend/target/classes/com/cyynote/WebApp.class new file mode 100644 index 0000000..9a95435 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/WebApp.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/controller/AuthController.class b/cyynote-backend/target/classes/com/cyynote/controller/AuthController.class new file mode 100644 index 0000000..5dddf51 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/controller/AuthController.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/controller/BaseController.class b/cyynote-backend/target/classes/com/cyynote/controller/BaseController.class new file mode 100644 index 0000000..ceed059 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/controller/BaseController.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/controller/DemoController.class b/cyynote-backend/target/classes/com/cyynote/controller/DemoController.class new file mode 100644 index 0000000..7434eb2 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/controller/DemoController.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/controller/JwtInterceptor.class b/cyynote-backend/target/classes/com/cyynote/controller/JwtInterceptor.class new file mode 100644 index 0000000..86a47f7 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/controller/JwtInterceptor.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/controller/NoteController.class b/cyynote-backend/target/classes/com/cyynote/controller/NoteController.class new file mode 100644 index 0000000..4b0b115 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/controller/NoteController.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/controller/Test2Controller.class b/cyynote-backend/target/classes/com/cyynote/controller/Test2Controller.class new file mode 100644 index 0000000..7138935 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/controller/Test2Controller.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/dso/AuthProcessorImpl.class b/cyynote-backend/target/classes/com/cyynote/dso/AuthProcessorImpl.class new file mode 100644 index 0000000..9fbabf4 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/dso/AuthProcessorImpl.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/dso/AuthSqlAnnotation.class b/cyynote-backend/target/classes/com/cyynote/dso/AuthSqlAnnotation.class new file mode 100644 index 0000000..c82380b Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/dso/AuthSqlAnnotation.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/dso/DsHelper.class b/cyynote-backend/target/classes/com/cyynote/dso/DsHelper.class new file mode 100644 index 0000000..a7c35fb Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/dso/DsHelper.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/dso/NoteSqlAnnotation.class b/cyynote-backend/target/classes/com/cyynote/dso/NoteSqlAnnotation.class new file mode 100644 index 0000000..84664e4 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/dso/NoteSqlAnnotation.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/dso/SqlAnnotation.class b/cyynote-backend/target/classes/com/cyynote/dso/SqlAnnotation.class new file mode 100644 index 0000000..70a9d30 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/dso/SqlAnnotation.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/dso/SqlMapper.class b/cyynote-backend/target/classes/com/cyynote/dso/SqlMapper.class new file mode 100644 index 0000000..af14e1c Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/dso/SqlMapper.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/model/Appx.class b/cyynote-backend/target/classes/com/cyynote/model/Appx.class new file mode 100644 index 0000000..7f37611 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/model/Appx.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/model/AppxModel.class b/cyynote-backend/target/classes/com/cyynote/model/AppxModel.class new file mode 100644 index 0000000..93db664 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/model/AppxModel.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/model/HistoryModel.class b/cyynote-backend/target/classes/com/cyynote/model/HistoryModel.class new file mode 100644 index 0000000..0e1d567 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/model/HistoryModel.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/model/NoteModel.class b/cyynote-backend/target/classes/com/cyynote/model/NoteModel.class new file mode 100644 index 0000000..5558509 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/model/NoteModel.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/model/UserModel.class b/cyynote-backend/target/classes/com/cyynote/model/UserModel.class new file mode 100644 index 0000000..806b33c Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/model/UserModel.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/payload/request/LoginRequest.class b/cyynote-backend/target/classes/com/cyynote/payload/request/LoginRequest.class new file mode 100644 index 0000000..3ebda55 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/payload/request/LoginRequest.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/payload/request/NoteRequest.class b/cyynote-backend/target/classes/com/cyynote/payload/request/NoteRequest.class new file mode 100644 index 0000000..a17a85b Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/payload/request/NoteRequest.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/payload/request/SignupRequest.class b/cyynote-backend/target/classes/com/cyynote/payload/request/SignupRequest.class new file mode 100644 index 0000000..df371e0 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/payload/request/SignupRequest.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/payload/request/UpdatePasswordRequest.class b/cyynote-backend/target/classes/com/cyynote/payload/request/UpdatePasswordRequest.class new file mode 100644 index 0000000..9381bce Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/payload/request/UpdatePasswordRequest.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/payload/response/JwtResponse.class b/cyynote-backend/target/classes/com/cyynote/payload/response/JwtResponse.class new file mode 100644 index 0000000..d54e35c Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/payload/response/JwtResponse.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/payload/response/MessageResponse.class b/cyynote-backend/target/classes/com/cyynote/payload/response/MessageResponse.class new file mode 100644 index 0000000..539b8c5 Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/payload/response/MessageResponse.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/util/TreeBuild.class b/cyynote-backend/target/classes/com/cyynote/util/TreeBuild.class new file mode 100644 index 0000000..9c78f4a Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/util/TreeBuild.class differ diff --git a/cyynote-backend/target/classes/com/cyynote/util/TreeNode.class b/cyynote-backend/target/classes/com/cyynote/util/TreeNode.class new file mode 100644 index 0000000..96ce38c Binary files /dev/null and b/cyynote-backend/target/classes/com/cyynote/util/TreeNode.class differ diff --git a/cyynote-backend/target/classes/db/schema.sql b/cyynote-backend/target/classes/db/schema.sql new file mode 100644 index 0000000..5c54a78 --- /dev/null +++ b/cyynote-backend/target/classes/db/schema.sql @@ -0,0 +1,53 @@ + + + + +CREATE TABLE `user` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT, + password TEXT, + username TEXT, + role TEXT, + token TEXT, + logintime TEXT +); +CREATE TABLE `note` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pid INTEGER, + title TEXT, + context TEXT, + conjson TEXT, + createtime TEXT, + updatetime TEXT, + viewtime TEXT, + userid INTEGER, + username TEXT, + flag INTEGER DEFAULT 1 +); +CREATE TABLE `history` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + nid INTEGER, + title TEXT, + context TEXT, + conjson TEXT, + createtime TEXT, + userid INTEGER, + username TEXT +); +CREATE TABLE appx ( + app_id INTEGER PRIMARY KEY AUTOINCREMENT, + app_key TEXT, + akey TEXT, + ugroup_id INTEGER DEFAULT 0, + agroup_id INTEGER, + name TEXT, + note TEXT, + ar_is_setting INTEGER NOT NULL DEFAULT 0, + ar_is_examine INTEGER NOT NULL DEFAULT 0, + ar_examine_ver INTEGER NOT NULL DEFAULT 0, + log_fulltime TEXT +); + +INSERT INTO `user` VALUES ('1', 'aaaaaaaaa', 'cyy123', 'admin', 'admin','',''); +INSERT INTO `user` VALUES ('2', 'aaaaaaaaa', 'test', 'test', 'test','',''); +INSERT INTO `note` VALUES ('1', '0', 'test', 'test', 'test','','','','1','1','1'); \ No newline at end of file diff --git a/cyynote-backend/target/classes/wood/SqlMapper.xml b/cyynote-backend/target/classes/wood/SqlMapper.xml new file mode 100644 index 0000000..1a86e76 --- /dev/null +++ b/cyynote-backend/target/classes/wood/SqlMapper.xml @@ -0,0 +1,31 @@ + + + + select app_id from appx limit 1 + + + + + + + + select * from appx where app_id = @{app_id:int} limit 1 + + + + select * from appx where app_id > @{app_id:int} order by app_id asc limit 4 + + + + + select app_id from appx limit 4 + + + + select app_id from appx limit 4 + + + \ No newline at end of file diff --git a/cyynote-backend/target/cyynote-0.2.jar b/cyynote-backend/target/cyynote-0.2.jar new file mode 100644 index 0000000..e5f645a Binary files /dev/null and b/cyynote-backend/target/cyynote-0.2.jar differ diff --git a/cyynote-backend/target/cyynote.jar.original b/cyynote-backend/target/cyynote.jar.original new file mode 100644 index 0000000..88f79e4 Binary files /dev/null and b/cyynote-backend/target/cyynote.jar.original differ diff --git a/cyynote-backend/target/maven-archiver/pom.properties b/cyynote-backend/target/maven-archiver/pom.properties new file mode 100644 index 0000000..7d28d87 --- /dev/null +++ b/cyynote-backend/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Wed Feb 05 19:29:06 CST 2025 +groupId=com.cyynote +artifactId=cyynote +version=1.0.0 diff --git a/cyynote-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/cyynote-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..7c4a5f5 --- /dev/null +++ b/cyynote-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,27 @@ +com\cyynote\payload\request\UpdatePasswordRequest.class +com\cyynote\controller\BaseController.class +com\cyynote\dso\DsHelper.class +com\cyynote\model\HistoryModel.class +com\cyynote\model\Appx.class +com\cyynote\payload\request\SignupRequest.class +com\cyynote\dso\AuthProcessorImpl.class +com\cyynote\controller\Test2Controller.class +com\cyynote\payload\response\MessageResponse.class +com\cyynote\WebApp.class +com\cyynote\dso\AuthSqlAnnotation.class +com\cyynote\controller\JwtInterceptor.class +com\cyynote\model\AppxModel.class +com\cyynote\controller\DemoController.class +com\cyynote\Config.class +com\cyynote\util\TreeBuild.class +com\cyynote\dso\SqlMapper.class +com\cyynote\model\UserModel.class +com\cyynote\payload\response\JwtResponse.class +com\cyynote\dso\NoteSqlAnnotation.class +com\cyynote\payload\request\LoginRequest.class +com\cyynote\controller\NoteController.class +com\cyynote\payload\request\NoteRequest.class +com\cyynote\util\TreeNode.class +com\cyynote\model\NoteModel.class +com\cyynote\dso\SqlAnnotation.class +com\cyynote\controller\AuthController.class diff --git a/cyynote-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/cyynote-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..97c77ce --- /dev/null +++ b/cyynote-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,27 @@ +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\Config.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\controller\AuthController.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\controller\BaseController.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\controller\DemoController.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\controller\JwtInterceptor.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\controller\NoteController.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\controller\Test2Controller.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\dso\AuthProcessorImpl.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\dso\AuthSqlAnnotation.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\dso\DsHelper.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\dso\NoteSqlAnnotation.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\dso\SqlAnnotation.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\dso\SqlMapper.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\model\Appx.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\model\AppxModel.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\model\HistoryModel.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\model\NoteModel.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\model\UserModel.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\payload\request\LoginRequest.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\payload\request\NoteRequest.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\payload\request\SignupRequest.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\payload\request\UpdatePasswordRequest.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\payload\response\JwtResponse.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\payload\response\MessageResponse.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\util\TreeBuild.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\util\TreeNode.java +D:\chuyy_code\cyynote\cyynote-backend\src\main\java\com\cyynote\WebApp.java diff --git a/cyynote-frontend/.eslintrc.cjs b/cyynote-frontend/.eslintrc.cjs new file mode 100644 index 0000000..d6c9537 --- /dev/null +++ b/cyynote-frontend/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/cyynote-frontend/.gitignore b/cyynote-frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/cyynote-frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/cyynote-frontend/README.md b/cyynote-frontend/README.md new file mode 100644 index 0000000..0d6babe --- /dev/null +++ b/cyynote-frontend/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +} +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/cyynote-frontend/dist.zip b/cyynote-frontend/dist.zip new file mode 100644 index 0000000..dff54a4 Binary files /dev/null and b/cyynote-frontend/dist.zip differ diff --git a/cyynote-frontend/dist0205.zip b/cyynote-frontend/dist0205.zip new file mode 100644 index 0000000..f536448 Binary files /dev/null and b/cyynote-frontend/dist0205.zip differ diff --git a/cyynote-frontend/index.html b/cyynote-frontend/index.html new file mode 100644 index 0000000..701f9ed --- /dev/null +++ b/cyynote-frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + CyyNote + + +
+ + + diff --git a/cyynote-frontend/package.json b/cyynote-frontend/package.json new file mode 100644 index 0000000..ebe6a27 --- /dev/null +++ b/cyynote-frontend/package.json @@ -0,0 +1,80 @@ + +{ + "name": "cyynote", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@alkhipce/editorjs-react": "^0.1.4", + "@blocknote/core": "^0.10.0", + "@blocknote/react": "^0.10.0", + "@douyinfe/semi-icons": "^2.48.0", + "@douyinfe/semi-icons-lab": "^2.48.0", + "@douyinfe/semi-ui": "^2.48.0", + "@editorjs/checklist": "^1.6.0", + "@editorjs/code": "^2.9.0", + "@editorjs/delimiter": "^1.4.0", + "@editorjs/editorjs": "^2.28.2", + "@editorjs/embed": "^2.7.0", + "@editorjs/header": "^2.8.1", + "@editorjs/image": "^2.9.0", + "@editorjs/inline-code": "^1.5.0", + "@editorjs/link": "^2.6.2", + "@editorjs/list": "^1.9.0", + "@editorjs/marker": "^1.4.0", + "@editorjs/paragraph": "^2.11.3", + "@editorjs/quote": "^2.6.0", + "@editorjs/raw": "^2.5.0", + "@editorjs/simple-image": "^1.6.0", + "@editorjs/table": "^2.3.0", + "@editorjs/warning": "^1.4.0", + "@excalidraw/excalidraw": "^0.17.2", + "@lexical/clipboard": "^0.12.5", + "@lexical/code": "^0.12.5", + "@lexical/file": "^0.12.5", + "@lexical/hashtag": "^0.12.5", + "@lexical/link": "^0.12.5", + "@lexical/list": "^0.12.5", + "@lexical/mark": "^0.12.5", + "@lexical/overflow": "^0.12.5", + "@lexical/plain-text": "^0.12.5", + "@lexical/react": "^0.12.5", + "@lexical/rich-text": "^0.12.5", + "@lexical/selection": "^0.12.5", + "@lexical/table": "^0.12.5", + "@lexical/utils": "^0.12.5", + "axios": "^1.6.2", + "editorjs-html": "^3.4.3", + "formik": "^2.4.5", + "html-react-parser": "^5.0.7", + "katex": "^0.16.9", + "lexical": "^0.12.5", + "prettier": "^3.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-editor-js": "^2.1.0", + "react-router-dom": "^6.20.1", + "sass": "^1.69.5", + "y-websocket": "^1.5.0", + "yjs": "^13.6.10", + "yup": "^1.3.2" + }, + "devDependencies": { + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/parser": "^6.10.0", + "@vitejs/plugin-react": "^4.2.0", + "eslint": "^8.53.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.4", + "typescript": "^5.2.2", + "vite": "^5.0.0" + } +} diff --git a/cyynote-frontend/public/vite.svg b/cyynote-frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/cyynote-frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cyynote-frontend/src/App.css b/cyynote-frontend/src/App.css new file mode 100644 index 0000000..8a02e97 --- /dev/null +++ b/cyynote-frontend/src/App.css @@ -0,0 +1,42 @@ +/*#root {*/ +/* max-width: 1280px;*/ +/* margin: 0 auto;*/ +/* padding: 2rem;*/ +/* text-align: center;*/ +/*}*/ + +/*.logo {*/ +/* height: 6em;*/ +/* padding: 1.5em;*/ +/* will-change: filter;*/ +/* transition: filter 300ms;*/ +/*}*/ +/*.logo:hover {*/ +/* filter: drop-shadow(0 0 2em #646cffaa);*/ +/*}*/ +/*.logo.react:hover {*/ +/* filter: drop-shadow(0 0 2em #61dafbaa);*/ +/*}*/ + +/*@keyframes logo-spin {*/ +/* from {*/ +/* transform: rotate(0deg);*/ +/* }*/ +/* to {*/ +/* transform: rotate(360deg);*/ +/* }*/ +/*}*/ + +/*@media (prefers-reduced-motion: no-preference) {*/ +/* a:nth-of-type(2) .logo {*/ +/* animation: logo-spin infinite 20s linear;*/ +/* }*/ +/*}*/ + +/*.card {*/ +/* padding: 2em;*/ +/*}*/ + +/*.read-the-docs {*/ +/* color: #888;*/ +/*}*/ diff --git a/cyynote-frontend/src/App.tsx b/cyynote-frontend/src/App.tsx new file mode 100644 index 0000000..ed88f7d --- /dev/null +++ b/cyynote-frontend/src/App.tsx @@ -0,0 +1,163 @@ +import { Component } from "react"; +import { Routes, Route } from "react-router-dom"; +import "./App.css"; + +import AuthService from "./services/auth.service"; +import IUser from './types/user.type'; + +// import Login from "./components/login.component"; +import Register from "./components/register.component"; +// import Home from "./components/home.component"; +import Profile from "./components/profile.component"; +import BoardUser from "./components/board-user.component"; +import BoardModerator from "./components/board-moderator.component"; +import BoardAdmin from "./components/board-admin.component"; + + +import NotePage from "./pages/note-page.tsx"; +import EventBus from "./common/EventBus"; +import SignIn from "./pages/sign-in.tsx"; +import Test from "./pages/test.tsx"; +// "build": "tsc && vite build", + + +type Props = {}; + +type State = { + showModeratorBoard: boolean, + showAdminBoard: boolean, + currentUser: IUser | undefined +} + +class App extends Component { + constructor(props: Props) { + super(props); + this.logOut = this.logOut.bind(this); + + this.state = { + showModeratorBoard: false, + showAdminBoard: false, + currentUser: undefined, + }; + } + + componentDidMount() { + const user = AuthService.getCurrentUser(); + + if (user) { + this.setState({ + currentUser: user, + showModeratorBoard: user.roles.includes("ROLE_MODERATOR"), + showAdminBoard: user.roles.includes("ROLE_ADMIN"), + }); + } + + EventBus.on("logout", this.logOut); + } + + componentWillUnmount() { + EventBus.remove("logout", this.logOut); + } + + logOut() { + AuthService.logout(); + this.setState({ + showModeratorBoard: false, + showAdminBoard: false, + currentUser: undefined, + }); + } + + render() { + // const { currentUser, showModeratorBoard, showAdminBoard } = this.state; + + return ( +
+ {/**/} + +
+ + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + } /> + } /> + +
+ + { /* */} +
+ ); + } +} + +export default App; diff --git a/cyynote-frontend/src/assets/react.svg b/cyynote-frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/cyynote-frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cyynote-frontend/src/common/EventBus.ts b/cyynote-frontend/src/common/EventBus.ts new file mode 100644 index 0000000..95d7efb --- /dev/null +++ b/cyynote-frontend/src/common/EventBus.ts @@ -0,0 +1,13 @@ +const eventBus = { + on(event: string, callback: EventListener) { + document.addEventListener(event, (e) => callback(e)); + }, + dispatch(event: string, data?: any) { + document.dispatchEvent(new CustomEvent(event, { detail: data })); + }, + remove(event: string, callback: EventListener) { + document.removeEventListener(event, callback); + }, +}; + +export default eventBus; diff --git a/cyynote-frontend/src/components/board-admin.component.tsx b/cyynote-frontend/src/components/board-admin.component.tsx new file mode 100644 index 0000000..fa9d06d --- /dev/null +++ b/cyynote-frontend/src/components/board-admin.component.tsx @@ -0,0 +1,54 @@ +import { Component } from "react"; + +import UserService from "../services/user.service"; +import EventBus from "../common/EventBus"; + +type Props = {}; + +type State = { + content: string; +} + +export default class BoardAdmin extends Component { + constructor(props: Props) { + super(props); + + this.state = { + content: "" + }; + } + + componentDidMount() { + UserService.getAdminBoard().then( + response => { + this.setState({ + content: response.data + }); + }, + error => { + this.setState({ + content: + (error.response && + error.response.data && + error.response.data.message) || + error.message || + error.toString() + }); + + if (error.response && error.response.status === 401) { + EventBus.dispatch("logout"); + } + } + ); + } + + render() { + return ( +
+
+

{this.state.content}

+
+
+ ); + } +} diff --git a/cyynote-frontend/src/components/board-moderator.component.tsx b/cyynote-frontend/src/components/board-moderator.component.tsx new file mode 100644 index 0000000..be468e2 --- /dev/null +++ b/cyynote-frontend/src/components/board-moderator.component.tsx @@ -0,0 +1,54 @@ +import { Component } from "react"; + +import UserService from "../services/user.service"; +import EventBus from "../common/EventBus"; + +type Props = {}; + +type State = { + content: string; +} + +export default class BoardAdmin extends Component { + constructor(props: Props) { + super(props); + + this.state = { + content: "" + }; + } + + componentDidMount() { + UserService.getModeratorBoard().then( + response => { + this.setState({ + content: response.data + }); + }, + error => { + this.setState({ + content: + (error.response && + error.response.data && + error.response.data.message) || + error.message || + error.toString() + }); + + if (error.response && error.response.status === 401) { + EventBus.dispatch("logout"); + } + } + ); + } + + render() { + return ( +
+
+

{this.state.content}

+
+
+ ); + } +} diff --git a/cyynote-frontend/src/components/board-user.component.tsx b/cyynote-frontend/src/components/board-user.component.tsx new file mode 100644 index 0000000..ca08c19 --- /dev/null +++ b/cyynote-frontend/src/components/board-user.component.tsx @@ -0,0 +1,54 @@ +import { Component } from "react"; + +import UserService from "../services/user.service"; +import EventBus from "../common/EventBus"; + +type Props = {}; + +type State = { + content: string; +} + +export default class BoardUser extends Component { + constructor(props: Props) { + super(props); + + this.state = { + content: "" + }; + } + + componentDidMount() { + UserService.getUserBoard().then( + response => { + this.setState({ + content: response.data + }); + }, + error => { + this.setState({ + content: + (error.response && + error.response.data && + error.response.data.message) || + error.message || + error.toString() + }); + + if (error.response && error.response.status === 401) { + EventBus.dispatch("logout"); + } + } + ); + } + + render() { + return ( +
+
+

{this.state.content}

+
+
+ ); + } +} diff --git a/cyynote-frontend/src/components/home.component.tsx b/cyynote-frontend/src/components/home.component.tsx new file mode 100644 index 0000000..bbf166e --- /dev/null +++ b/cyynote-frontend/src/components/home.component.tsx @@ -0,0 +1,47 @@ +import { Component } from "react"; + +import UserService from "../services/user.service"; + +type Props = {}; + +type State = { + content: string; +} + +export default class Home extends Component { + constructor(props: Props) { + super(props); + + this.state = { + content: "" + }; + } + + componentDidMount() { + UserService.getPublicContent().then( + response => { + this.setState({ + content: response.data + }); + }, + error => { + this.setState({ + content: + (error.response && error.response.data) || + error.message || + error.toString() + }); + } + ); + } + + render() { + return ( +
+
+

{this.state.content}

+
+
+ ); + } +} diff --git a/cyynote-frontend/src/components/index.css b/cyynote-frontend/src/components/index.css new file mode 100644 index 0000000..4692830 --- /dev/null +++ b/cyynote-frontend/src/components/index.css @@ -0,0 +1,1783 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +@import 'https://fonts.googleapis.com/css?family=Reenie+Beanie'; + +body { + margin: 0; + font-family: system-ui, -apple-system, BlinkMacSystemFont, '.SFNSText-Regular', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background: #eee; + padding: 0 20px; +} + +header { + max-width: 580px; + margin: auto; + position: relative; + display: flex; + justify-content: center; +} + +header a { + max-width: 220px; + margin: 20px 0 0 0; + display: block; +} + +header img { + display: block; + height: 100%; + width: 100%; +} + +header h1 { + text-align: left; + color: #333; + display: inline-block; + margin: 20px 0 0 0; +} + +.editor-shell { + margin: 20px auto; + border-radius: 2px; + max-width: 1100px; + color: #000; + position: relative; + line-height: 1.7; + font-weight: 400; +} + +.editor-shell .editor-container { + background: #fff; + position: relative; + display: block; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; +} + +.editor-shell .editor-container.tree-view { + border-radius: 0; +} + +.editor-shell .editor-container.plain-text { + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} + +.editor-scroller { + min-height: 150px; + border: 0; + display: flex; + position: relative; + outline: 0; + z-index: 0; + overflow: auto; + resize: vertical; +} + +.editor { + flex: auto; + position: relative; + resize: vertical; + z-index: -1; +} + +.test-recorder-output { + margin: 20px auto 20px auto; + width: 100%; +} + +pre { + line-height: 1.1; + background: #222; + color: #fff; + margin: 0; + padding: 10px; + font-size: 12px; + overflow: auto; + max-height: 400px; +} + +.tree-view-output { + display: block; + background: #222; + color: #fff; + padding: 0; + font-size: 12px; + margin: 1px auto 10px auto; + position: relative; + overflow: hidden; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; +} + +pre::-webkit-scrollbar { + background: transparent; + width: 10px; +} + +pre::-webkit-scrollbar-thumb { + background: #999; +} + +.editor-dev-button { + position: relative; + display: block; + width: 40px; + height: 40px; + font-size: 12px; + border-radius: 20px; + border: none; + cursor: pointer; + outline: none; + box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.3); + background-color: #444; +} + +.editor-dev-button::after { + content: ''; + position: absolute; + top: 10px; + right: 10px; + bottom: 10px; + left: 10px; + display: block; + background-size: contain; + filter: invert(1); +} + +.editor-dev-button:hover { + background-color: #555; +} + +.editor-dev-button.active { + background-color: rgb(233, 35, 35); +} + +.test-recorder-toolbar { + display: flex; +} + +.test-recorder-button { + position: relative; + display: block; + width: 32px; + height: 32px; + font-size: 10px; + padding: 6px 6px; + border-radius: 4px; + border: none; + cursor: pointer; + outline: none; + box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.4); + background-color: #222; + transition: box-shadow 50ms ease-out; +} + +.test-recorder-button:active { + box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.4); +} + +.test-recorder-button + .test-recorder-button { + margin-left: 4px; +} + +.test-recorder-button::after { + content: ''; + position: absolute; + top: 8px; + right: 8px; + bottom: 8px; + left: 8px; + display: block; + background-size: contain; + filter: invert(1); +} + +#options-button { + position: fixed; + left: 20px; + bottom: 20px; +} + +#test-recorder-button { + position: fixed; + left: 70px; + bottom: 20px; +} + +#paste-log-button { + position: fixed; + left: 120px; + bottom: 20px; +} + +#docs-button { + position: fixed; + left: 170px; + bottom: 20px; +} + +#options-button::after { + background-image: url(images/icons/gear.svg); +} + +#test-recorder-button::after { + background-image: url(images/icons/journal-code.svg); +} + +#paste-log-button::after { + background-image: url(images/icons/clipboard.svg); +} + +#docs-button::after { + background-image: url(images/icons/file-earmark-text.svg); +} + +#test-recorder-button-snapshot { + margin-right: auto; +} + +#test-recorder-button-snapshot::after { + background-image: url(images/icons/camera.svg); +} + +#test-recorder-button-copy::after { + background-image: url(images/icons/clipboard.svg); +} + +#test-recorder-button-download::after { + background-image: url(images/icons/download.svg); +} + +.typeahead-popover { + background: #fff; + box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3); + border-radius: 8px; + position: fixed; +} + +.typeahead-popover ul { + padding: 0; + list-style: none; + margin: 0; + border-radius: 8px; + max-height: 200px; + overflow-y: scroll; +} + +.typeahead-popover ul::-webkit-scrollbar { + display: none; +} + +.typeahead-popover ul { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.typeahead-popover ul li { + margin: 0; + min-width: 180px; + font-size: 14px; + outline: none; + cursor: pointer; + border-radius: 8px; +} + +.typeahead-popover ul li.selected { + background: #eee; +} + +.typeahead-popover li { + margin: 0 8px 0 8px; + padding: 8px; + color: #050505; + cursor: pointer; + line-height: 16px; + font-size: 15px; + display: flex; + align-content: center; + flex-direction: row; + flex-shrink: 0; + background-color: #fff; + border-radius: 8px; + border: 0; +} + +.typeahead-popover li.active { + display: flex; + width: 20px; + height: 20px; + background-size: contain; +} + +.typeahead-popover li:first-child { + border-radius: 8px 8px 0px 0px; +} + +.typeahead-popover li:last-child { + border-radius: 0px 0px 8px 8px; +} + +.typeahead-popover li:hover { + background-color: #eee; +} + +.typeahead-popover li .text { + display: flex; + line-height: 20px; + flex-grow: 1; + min-width: 150px; +} + +.typeahead-popover li .icon { + display: flex; + width: 20px; + height: 20px; + user-select: none; + margin-right: 8px; + line-height: 16px; + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.component-picker-menu { + width: 200px; +} + +.mentions-menu { + width: 250px; +} + +.auto-embed-menu { + width: 150px; +} + +.emoji-menu { + width: 200px; +} + +i.palette { + background-image: url(images/icons/palette.svg); +} + +i.bucket { + background-image: url(images/icons/paint-bucket.svg); +} + +i.bold { + background-image: url(images/icons/type-bold.svg); +} + +i.italic { + background-image: url(images/icons/type-italic.svg); +} + +i.clear { + background-image: url(images/icons/trash.svg); +} + +i.code { + background-image: url(images/icons/code.svg); +} + +i.underline { + background-image: url(images/icons/type-underline.svg); +} + +i.strikethrough { + background-image: url(images/icons/type-strikethrough.svg); +} + +i.subscript { + background-image: url(images/icons/type-subscript.svg); +} + +i.superscript { + background-image: url(images/icons/type-superscript.svg); +} + +i.link { + background-image: url(images/icons/link.svg); +} + +i.horizontal-rule { + background-image: url(images/icons/horizontal-rule.svg); +} + +.icon.plus { + background-image: url(images/icons/plus.svg); +} + +.icon.caret-right { + background-image: url(images/icons/caret-right-fill.svg); +} + +.icon.dropdown-more { + background-image: url(images/icons/dropdown-more.svg); +} + +.icon.font-color { + background-image: url(images/icons/font-color.svg); +} + +.icon.font-family { + background-image: url(images/icons/font-family.svg); +} + +.icon.bg-color { + background-image: url(images/icons/bg-color.svg); +} + +.icon.table { + background-color: #6c757d; + mask-image: url(images/icons/table.svg); + -webkit-mask-image: url(images/icons/table.svg); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; +} + +i.image { + background-image: url(images/icons/file-image.svg); +} + +i.table { + background-image: url(images/icons/table.svg); +} + +i.close { + background-image: url(images/icons/close.svg); +} + +i.figma { + background-image: url(images/icons/figma.svg); +} + +i.poll { + background-image: url(images/icons/card-checklist.svg); +} + +i.columns { + background-image: url(images/icons/3-columns.svg); +} + +i.tweet { + background-image: url(images/icons/tweet.svg); +} + +i.youtube { + background-image: url(images/icons/youtube.svg); +} + +.icon.left-align, +i.left-align { + background-image: url(images/icons/text-left.svg); +} + +.icon.center-align, +i.center-align { + background-image: url(images/icons/text-center.svg); +} + +.icon.right-align, +i.right-align { + background-image: url(images/icons/text-right.svg); +} + +.icon.justify-align, +i.justify-align { + background-image: url(images/icons/justify.svg); +} + +i.indent { + background-image: url(images/icons/indent.svg); +} + +i.markdown { + background-image: url(images/icons/markdown.svg); +} + +i.outdent { + background-image: url(images/icons/outdent.svg); +} + +i.undo { + background-image: url(images/icons/arrow-counterclockwise.svg); +} + +i.redo { + background-image: url(images/icons/arrow-clockwise.svg); +} + +i.sticky { + background-image: url(images/icons/sticky.svg); +} + +i.mic { + background-image: url(images/icons/mic.svg); +} + +i.import { + background-image: url(images/icons/upload.svg); +} + +i.export { + background-image: url(images/icons/download.svg); +} + +i.diagram-2 { + background-image: url(images/icons/diagram-2.svg); +} + +i.user { + background-image: url(images/icons/user.svg); +} + +i.equation { + background-image: url(images/icons/plus-slash-minus.svg); +} + +i.gif { + background-image: url(images/icons/filetype-gif.svg); +} + +i.copy { + background-image: url(images/icons/copy.svg); +} + +i.success { + background-image: url(images/icons/success.svg); +} + +i.prettier { + background-image: url(images/icons/prettier.svg); +} + +i.prettier-error { + background-image: url(images/icons/prettier-error.svg); +} + +i.page-break, +.icon.page-break { + background-image: url(images/icons/scissors.svg); +} + +.link-editor .button.active, +.toolbar .button.active { + background-color: rgb(223, 232, 250); +} + +.link-editor .link-input { + display: block; + width: calc(100% - 75px); + box-sizing: border-box; + margin: 12px 12px; + padding: 8px 12px; + border-radius: 15px; + background-color: #eee; + font-size: 15px; + color: rgb(5, 5, 5); + border: 0; + outline: 0; + position: relative; + font-family: inherit; +} + +.link-editor .link-view { + display: block; + width: calc(100% - 24px); + margin: 8px 12px; + padding: 8px 12px; + border-radius: 15px; + font-size: 15px; + color: rgb(5, 5, 5); + border: 0; + outline: 0; + position: relative; + font-family: inherit; +} + +.link-editor .link-view a { + display: block; + word-break: break-word; + width: calc(100% - 33px); +} + +.link-editor div.link-edit { + background-image: url(images/icons/pencil-fill.svg); + background-size: 16px; + background-position: center; + background-repeat: no-repeat; + width: 35px; + vertical-align: -0.25em; + position: absolute; + right: 30px; + top: 0; + bottom: 0; + cursor: pointer; +} + +.link-editor div.link-trash { + background-image: url(images/icons/trash.svg); + background-size: 16px; + background-position: center; + background-repeat: no-repeat; + width: 35px; + vertical-align: -0.25em; + position: absolute; + right: 0; + top: 0; + bottom: 0; + cursor: pointer; +} + +.link-editor div.link-cancel { + background-image: url(images/icons/close.svg); + background-size: 16px; + background-position: center; + background-repeat: no-repeat; + width: 35px; + vertical-align: -0.25em; + margin-right: 28px; + position: absolute; + right: 0; + top: 0; + bottom: 0; + cursor: pointer; +} + +.link-editor div.link-confirm { + background-image: url(images/icons/success-alt.svg); + background-size: 16px; + background-position: center; + background-repeat: no-repeat; + width: 35px; + vertical-align: -0.25em; + margin-right: 2px; + position: absolute; + right: 0; + top: 0; + bottom: 0; + cursor: pointer; +} + +.link-editor .link-input a { + color: rgb(33, 111, 219); + text-decoration: underline; + white-space: nowrap; + overflow: hidden; + margin-right: 30px; + text-overflow: ellipsis; +} + +.link-editor .link-input a:hover { + text-decoration: underline; +} + +.link-editor .font-size-wrapper, +.link-editor .font-family-wrapper { + display: flex; + margin: 0 4px; +} + +.link-editor select { + padding: 6px; + border: none; + background-color: rgba(0, 0, 0, 0.075); + border-radius: 4px; +} + +.mention:focus { + box-shadow: rgb(180 213 255) 0px 0px 0px 2px; + outline: none; +} + +.characters-limit { + color: #888; + font-size: 12px; + text-align: right; + display: block; + position: absolute; + left: 12px; + bottom: 5px; +} + +.characters-limit.characters-limit-exceeded { + color: red; +} + +.dropdown { + z-index: 100; + display: block; + position: fixed; + box-shadow: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1), + inset 0 0 0 1px rgba(255, 255, 255, 0.5); + border-radius: 8px; + min-height: 40px; + background-color: #fff; +} + +.dropdown .item { + margin: 0 8px 0 8px; + padding: 8px; + color: #050505; + cursor: pointer; + line-height: 16px; + font-size: 15px; + display: flex; + align-content: center; + flex-direction: row; + flex-shrink: 0; + justify-content: space-between; + background-color: #fff; + border-radius: 8px; + border: 0; + max-width: 250px; + min-width: 100px; +} + +.dropdown .item.fontsize-item, +.dropdown .item.fontsize-item .text { + min-width: unset; +} + +.dropdown .item .active { + display: flex; + width: 20px; + height: 20px; + background-size: contain; +} + +.dropdown .item:first-child { + margin-top: 8px; +} + +.dropdown .item:last-child { + margin-bottom: 8px; +} + +.dropdown .item:hover { + background-color: #eee; +} + +.dropdown .item .text { + display: flex; + line-height: 20px; + flex-grow: 1; + min-width: 150px; +} + +.dropdown .item .icon { + display: flex; + width: 20px; + height: 20px; + user-select: none; + margin-right: 12px; + line-height: 16px; + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + +.dropdown .divider { + width: auto; + background-color: #eee; + margin: 4px 8px; + height: 1px; +} + +@media screen and (max-width: 1100px) { + .dropdown-button-text { + display: none !important; + } + + .font-size .dropdown-button-text { + display: flex !important; + } + + .code-language .dropdown-button-text { + display: flex !important; + } +} + +.icon.paragraph { + background-image: url(images/icons/text-paragraph.svg); +} + +.icon.h1 { + background-image: url(images/icons/type-h1.svg); +} + +.icon.h2 { + background-image: url(images/icons/type-h2.svg); +} + +.icon.h3 { + background-image: url(images/icons/type-h3.svg); +} + +.icon.h4 { + background-image: url(images/icons/type-h4.svg); +} + +.icon.h5 { + background-image: url(images/icons/type-h5.svg); +} + +.icon.h6 { + background-image: url(images/icons/type-h6.svg); +} + +.icon.bullet-list, +.icon.bullet { + background-image: url(images/icons/list-ul.svg); +} + +.icon.check-list, +.icon.check { + background-image: url(images/icons/square-check.svg); +} + +.icon.numbered-list, +.icon.number { + background-image: url(images/icons/list-ol.svg); +} + +.icon.quote { + background-image: url(images/icons/chat-square-quote.svg); +} + +.icon.code { + background-image: url(images/icons/code.svg); +} + +.switches { + z-index: 6; + position: fixed; + left: 10px; + bottom: 70px; + animation: slide-in 0.4s ease; +} + +@keyframes slide-in { + 0% { + opacity: 0; + transform: translateX(-200px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.switch { + display: block; + color: #444; + margin: 5px 0; + background-color: rgba(238, 238, 238, 0.7); + padding: 5px 10px; + border-radius: 10px; +} + +#rich-text-switch { + right: 0; +} + +#character-count-switch { + right: 130px; +} + +.switch label { + margin-right: 5px; + line-height: 24px; + width: 100px; + font-size: 14px; + display: inline-block; + vertical-align: middle; +} + +.switch button { + background-color: rgb(206, 208, 212); + height: 24px; + box-sizing: border-box; + border-radius: 12px; + width: 44px; + display: inline-block; + vertical-align: middle; + position: relative; + outline: none; + cursor: pointer; + transition: background-color 0.1s; + border: 2px solid transparent; +} + +.switch button:focus-visible { + border-color: blue; +} + +.switch button span { + top: 0px; + left: 0px; + display: block; + position: absolute; + width: 20px; + height: 20px; + border-radius: 12px; + background-color: white; + transition: transform 0.2s; +} + +.switch button[aria-checked='true'] { + background-color: rgb(24, 119, 242); +} + +.switch button[aria-checked='true'] span { + transform: translateX(20px); +} + +.editor-shell span.editor-image { + cursor: default; + display: inline-block; + position: relative; + user-select: none; +} + +.editor-shell .editor-image img { + max-width: 100%; + cursor: default; +} + +.editor-shell .editor-image img.focused { + outline: 2px solid rgb(60, 132, 244); + user-select: none; +} + +.editor-shell .editor-image img.focused.draggable { + cursor: grab; +} + +.editor-shell .editor-image img.focused.draggable:active { + cursor: grabbing; +} + +.editor-shell .editor-image .image-caption-container .tree-view-output { + margin: 0; + border-radius: 0; +} + +.editor-shell .editor-image .image-caption-container { + display: block; + position: absolute; + bottom: 4px; + left: 0; + right: 0; + padding: 0; + margin: 0; + border-top: 1px solid #fff; + background-color: rgba(255, 255, 255, 0.9); + min-width: 100px; + color: #000; + overflow: hidden; +} + +.editor-shell .editor-image .image-caption-button { + display: block; + position: absolute; + bottom: 20px; + left: 0; + right: 0; + width: 30%; + padding: 10px; + margin: 0 auto; + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 5px; + background-color: rgba(0, 0, 0, 0.5); + min-width: 100px; + color: #fff; + cursor: pointer; + user-select: none; +} + +.editor-shell .editor-image .image-caption-button:hover { + background-color: rgba(60, 132, 244, 0.5); +} + +.editor-shell .editor-image .image-edit-button { + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: 5px; + background-image: url(/src/images/icons/pencil-fill.svg); + background-size: 16px; + background-position: center; + background-repeat: no-repeat; + width: 35px; + height: 35px; + vertical-align: -0.25em; + position: absolute; + right: 4px; + top: 4px; + cursor: pointer; + user-select: none; +} + +.editor-shell .editor-image .image-edit-button:hover { + background-color: rgba(60, 132, 244, 0.1); +} + +.editor-shell .editor-image .image-resizer { + display: block; + width: 7px; + height: 7px; + position: absolute; + background-color: rgb(60, 132, 244); + border: 1px solid #fff; +} + +.editor-shell .editor-image .image-resizer.image-resizer-n { + top: -6px; + left: 48%; + cursor: n-resize; +} + +.editor-shell .editor-image .image-resizer.image-resizer-ne { + top: -6px; + right: -6px; + cursor: ne-resize; +} + +.editor-shell .editor-image .image-resizer.image-resizer-e { + bottom: 48%; + right: -6px; + cursor: e-resize; +} + +.editor-shell .editor-image .image-resizer.image-resizer-se { + bottom: -2px; + right: -6px; + cursor: nwse-resize; +} + +.editor-shell .editor-image .image-resizer.image-resizer-s { + bottom: -2px; + left: 48%; + cursor: s-resize; +} + +.editor-shell .editor-image .image-resizer.image-resizer-sw { + bottom: -2px; + left: -6px; + cursor: sw-resize; +} + +.editor-shell .editor-image .image-resizer.image-resizer-w { + bottom: 48%; + left: -6px; + cursor: w-resize; +} + +.editor-shell .editor-image .image-resizer.image-resizer-nw { + top: -6px; + left: -6px; + cursor: nw-resize; +} + +.editor-shell span.inline-editor-image { + cursor: default; + display: inline-block; + position: relative; + z-index: 1; +} + +.editor-shell .inline-editor-image img { + max-width: 100%; + cursor: default; +} + +.editor-shell .inline-editor-image img.focused { + outline: 2px solid rgb(60, 132, 244); +} + +.editor-shell .inline-editor-image img.focused.draggable { + cursor: grab; +} + +.editor-shell .inline-editor-image img.focused.draggable:active { + cursor: grabbing; +} + +.editor-shell .inline-editor-image .image-caption-container .tree-view-output { + margin: 0; + border-radius: 0; +} + +.editor-shell .inline-editor-image.position-full { + margin: 1em 0 1em 0; +} + +.editor-shell .inline-editor-image.position-left { + float: left; + width: 50%; + margin: 1em 1em 0 0; +} + +.editor-shell .inline-editor-image.position-right { + float: right; + width: 50%; + margin: 1em 0 0 1em; +} + +.editor-shell .inline-editor-image .image-edit-button { + display: block; + position: absolute; + top: 12px; + right: 12px; + padding: 6px 8px; + margin: 0 auto; + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 5px; + background-color: rgba(0, 0, 0, 0.5); + min-width: 60px; + color: #fff; + cursor: pointer; + user-select: none; +} + +.editor-shell .inline-editor-image .image-edit-button:hover { + background-color: rgba(60, 132, 244, 0.5); +} + +.editor-shell .inline-editor-image .image-caption-container { + display: block; + background-color: #f4f4f4; + min-width: 100%; + color: #000; + overflow: hidden; +} + +.emoji { + color: transparent; + caret-color: rgb(5, 5, 5); + background-size: 16px 16px; + background-position: center; + background-repeat: no-repeat; + vertical-align: middle; + margin: 0 -1px; +} + +.emoji-inner { + padding: 0 0.15em; +} + +.emoji-inner::selection { + color: transparent; + background-color: rgba(150, 150, 150, 0.4); +} + +.emoji-inner::moz-selection { + color: transparent; + background-color: rgba(150, 150, 150, 0.4); +} + +.emoji.happysmile { + background-image: url(images/emoji/1F642.png); +} + +.emoji.veryhappysmile { + background-image: url(images/emoji/1F600.png); +} + +.emoji.unhappysmile { + background-image: url(images/emoji/1F641.png); +} + +.emoji.heart { + background-image: url(images/emoji/2764.png); +} + +.keyword { + color: rgb(241, 118, 94); + font-weight: bold; +} + +.actions { + position: absolute; + text-align: right; + margin: 10px; + bottom: 0; + right: 0; +} + +.actions.tree-view { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.actions i { + background-size: contain; + display: inline-block; + height: 15px; + width: 15px; + vertical-align: -0.25em; +} + +.actions i.indent { + background-image: url(images/icons/indent.svg); +} + +.actions i.outdent { + background-image: url(images/icons/outdent.svg); +} + +.actions i.lock { + background-image: url(images/icons/lock-fill.svg); +} + +.actions i.image { + background-image: url(images/icons/file-image.svg); +} + +.actions i.table { + background-image: url(images/icons/table.svg); +} + +.actions i.unlock { + background-image: url(images/icons/lock.svg); +} + +.actions i.left-align { + background-image: url(images/icons/text-left.svg); +} + +.actions i.center-align { + background-image: url(images/icons/text-center.svg); +} + +.actions i.right-align { + background-image: url(images/icons/text-right.svg); +} + +.actions i.justify-align { + background-image: url(images/icons/justify.svg); +} + +.actions i.disconnect { + background-image: url(images/icons/plug.svg); +} + +.actions i.connect { + background-image: url(images/icons/plug-fill.svg); +} + +.table-cell-action-button-container { + position: absolute; + top: 0; + left: 0; + will-change: transform; +} + +.table-cell-action-button { + background-color: none; + display: flex; + justify-content: center; + align-items: center; + border: 0; + position: relative; + border-radius: 15px; + color: #222; + display: inline-block; + cursor: pointer; +} + +i.chevron-down { + background-color: transparent; + background-size: contain; + display: inline-block; + height: 8px; + width: 8px; + background-image: url(images/icons/chevron-down.svg); +} + +.action-button { + background-color: #eee; + border: 0; + padding: 8px 12px; + position: relative; + margin-left: 5px; + border-radius: 15px; + color: #222; + display: inline-block; + cursor: pointer; +} + +.action-button:hover { + background-color: #ddd; + color: #000; +} + +.action-button-mic.active { + animation: mic-pulsate-color 3s infinite; +} + +button.action-button:disabled { + opacity: 0.6; + background: #eee; + cursor: not-allowed; +} + +@keyframes mic-pulsate-color { + 0% { + background-color: #ffdcdc; + } + + 50% { + background-color: #ff8585; + } + + 100% { + background-color: #ffdcdc; + } +} + +.debug-timetravel-panel { + overflow: hidden; + padding: 0 0 10px 0; + margin: auto; + display: flex; +} + +.debug-timetravel-panel-slider { + padding: 0; + flex: 8; +} + +.debug-timetravel-panel-button { + padding: 0; + border: 0; + background: none; + flex: 1; + color: #fff; + font-size: 12px; +} + +.debug-timetravel-panel-button:hover { + text-decoration: underline; +} + +.debug-timetravel-button { + border: 0; + padding: 0; + font-size: 12px; + top: 10px; + right: 15px; + position: absolute; + background: none; + color: #fff; +} + +.debug-timetravel-button:hover { + text-decoration: underline; +} + +.debug-treetype-button { + border: 0; + padding: 0; + font-size: 12px; + top: 10px; + right: 85px; + position: absolute; + background: none; + color: #fff; +} + +.debug-treetype-button:hover { + text-decoration: underline; +} + +.connecting { + font-size: 15px; + color: #999; + overflow: hidden; + position: absolute; + text-overflow: ellipsis; + top: 10px; + left: 10px; + user-select: none; + white-space: nowrap; + display: inline-block; + pointer-events: none; +} + +.ltr { + text-align: left; +} + +.rtl { + text-align: right; +} + +.toolbar { + display: flex; + margin-bottom: 1px; + background: #fff; + padding: 4px; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + vertical-align: middle; + overflow: auto; + height: 36px; + position: sticky; + top: 0; + z-index: 2; +} + +button.toolbar-item { + border: 0; + display: flex; + background: none; + border-radius: 10px; + padding: 8px; + cursor: pointer; + vertical-align: middle; + flex-shrink: 0; + align-items: center; + justify-content: space-between; +} + +button.toolbar-item:disabled { + cursor: not-allowed; +} + +button.toolbar-item.spaced { + margin-right: 2px; +} + +button.toolbar-item i.format { + background-size: contain; + display: inline-block; + height: 18px; + width: 18px; + vertical-align: -0.25em; + display: flex; + opacity: 0.6; +} + +button.toolbar-item:disabled .icon, +button.toolbar-item:disabled .text, +button.toolbar-item:disabled i.format, +button.toolbar-item:disabled .chevron-down { + opacity: 0.2; +} + +button.toolbar-item.active { + background-color: rgba(223, 232, 250, 0.3); +} + +button.toolbar-item.active i { + opacity: 1; +} + +.toolbar-item:hover:not([disabled]) { + background-color: #eee; +} + +.toolbar-item.font-family .text { + display: block; + max-width: 40px; +} + +.toolbar .code-language { + width: 150px; +} + +.toolbar .toolbar-item .text { + display: flex; + line-height: 20px; + vertical-align: middle; + font-size: 14px; + color: #777; + text-overflow: ellipsis; + overflow: hidden; + height: 20px; + text-align: left; + padding-right: 10px; +} + +.toolbar .toolbar-item .icon { + display: flex; + width: 20px; + height: 20px; + user-select: none; + margin-right: 8px; + line-height: 16px; + background-size: contain; +} + +.toolbar i.chevron-down, +.toolbar-item i.chevron-down { + margin-top: 3px; + width: 16px; + height: 16px; + display: flex; + user-select: none; +} + +.toolbar i.chevron-down.inside { + width: 16px; + height: 16px; + display: flex; + margin-left: -25px; + margin-top: 11px; + margin-right: 10px; + pointer-events: none; +} + +.toolbar .divider { + width: 1px; + background-color: #eee; + margin: 0 4px; +} + +.sticky-note-container { + position: absolute; + z-index: 9; + width: 120px; + display: inline-block; +} + +.sticky-note { + line-height: 1; + text-align: left; + width: 120px; + margin: 25px; + padding: 20px 10px; + position: relative; + border: 1px solid #e8e8e8; + font-family: 'Reenie Beanie'; + font-size: 24px; + border-bottom-right-radius: 60px 5px; + display: block; + cursor: move; +} + +.sticky-note:after { + content: ''; + position: absolute; + z-index: -1; + right: -0px; + bottom: 20px; + width: 120px; + height: 25px; + background: rgba(0, 0, 0, 0.2); + box-shadow: 2px 15px 5px rgba(0, 0, 0, 0.4); + transform: matrix(-1, -0.1, 0, 1, 0, 0); +} + +.sticky-note.yellow { + border-top: 1px solid #fdfd86; + background: linear-gradient( + 135deg, + #ffff88 81%, + #ffff88 82%, + #ffff88 82%, + #ffffc6 100% + ); +} + +.sticky-note.pink { + border-top: 1px solid #e7d1e4; + background: linear-gradient( + 135deg, + #f7cbe8 81%, + #f7cbe8 82%, + #f7cbe8 82%, + #e7bfe1 100% + ); +} + +.sticky-note-container.dragging { + transition: none !important; +} + +.sticky-note div { + cursor: text; +} + +.sticky-note .delete { + border: 0; + background: none; + position: absolute; + top: 8px; + right: 10px; + font-size: 10px; + cursor: pointer; + opacity: 0.5; +} + +.sticky-note .delete:hover { + font-weight: bold; + opacity: 1; +} + +.sticky-note .color { + border: 0; + background: none; + position: absolute; + top: 8px; + right: 25px; + cursor: pointer; + opacity: 0.5; +} + +.sticky-note .color:hover { + opacity: 1; +} + +.sticky-note .color i { + display: block; + width: 12px; + height: 12px; + background-size: contain; +} + +.excalidraw-button { + border: 0; + padding: 0; + margin: 0; + background-color: transparent; +} + +.excalidraw-button.selected { + outline: 2px solid rgb(60, 132, 244); + user-select: none; +} + +.github-corner:hover .octo-arm { + animation: octocat-wave 560ms ease-in-out; +} + +@keyframes octocat-wave { + 0%, + 100% { + transform: rotate(0); + } + + 20%, + 60% { + transform: rotate(-25deg); + } + + 40%, + 80% { + transform: rotate(10deg); + } +} + +@media (max-width: 500px) { + .github-corner:hover .octo-arm { + animation: none; + } + + .github-corner .octo-arm { + animation: octocat-wave 560ms ease-in-out; + } +} + +.spacer { + letter-spacing: -2px; +} + +.editor-equation { + cursor: default; + user-select: none; +} + +.editor-equation.focused { + outline: 2px solid rgb(60, 132, 244); +} + +button.item i { + opacity: 0.6; +} + +button.item.dropdown-item-active { + background-color: rgba(223, 232, 250, 0.3); +} + +button.item.dropdown-item-active i { + opacity: 1; +} + +hr { + padding: 2px 2px; + border: none; + margin: 1em 0; + cursor: pointer; +} + +hr:after { + content: ''; + display: block; + height: 2px; + background-color: #ccc; + line-height: 2px; +} + +hr.selected { + outline: 2px solid rgb(60, 132, 244); + user-select: none; +} + +.TableNode__contentEditable { + min-height: 20px; + border: 0px; + resize: none; + cursor: text; + display: block; + position: relative; + outline: 0px; + padding: 0; + user-select: text; + font-size: 15px; + white-space: pre-wrap; + word-break: break-word; + z-index: 3; +} + +.PlaygroundEditorTheme__blockCursor { + display: block; + pointer-events: none; + position: absolute; +} + +.PlaygroundEditorTheme__blockCursor:after { + content: ''; + display: block; + position: absolute; + top: -2px; + width: 20px; + border-top: 1px solid black; + animation: CursorBlink 1.1s steps(2, start) infinite; +} + +@keyframes CursorBlink { + to { + visibility: hidden; + } +} + +.dialog-dropdown { + background-color: #eee !important; + margin-bottom: 10px; + width: 100%; +} diff --git a/cyynote-frontend/src/components/lexical/Editor.tsx b/cyynote-frontend/src/components/lexical/Editor.tsx new file mode 100644 index 0000000..1dba7c5 --- /dev/null +++ b/cyynote-frontend/src/components/lexical/Editor.tsx @@ -0,0 +1,278 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin'; +import {CharacterLimitPlugin} from '@lexical/react/LexicalCharacterLimitPlugin'; +import {CheckListPlugin} from '@lexical/react/LexicalCheckListPlugin'; +import {ClearEditorPlugin} from '@lexical/react/LexicalClearEditorPlugin'; +import LexicalClickableLinkPlugin from '@lexical/react/LexicalClickableLinkPlugin'; +import {CollaborationPlugin} from '@lexical/react/LexicalCollaborationPlugin'; +import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; +import {HashtagPlugin} from '@lexical/react/LexicalHashtagPlugin'; +import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin'; +import {HorizontalRulePlugin} from '@lexical/react/LexicalHorizontalRulePlugin'; +import {ListPlugin} from '@lexical/react/LexicalListPlugin'; +import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin'; +import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin'; +import {TabIndentationPlugin} from '@lexical/react/LexicalTabIndentationPlugin'; +import {TablePlugin} from '@lexical/react/LexicalTablePlugin'; +import useLexicalEditable from '@lexical/react/useLexicalEditable'; +import {useEffect, useState,useLayoutEffect} from 'react'; +import {CAN_USE_DOM} from './shared/src/canUseDOM'; + +import {createWebsocketProvider} from './collaboration'; +import {useSettings} from './context/SettingsContext'; +import {useSharedHistoryContext} from './context/SharedHistoryContext'; +import ActionsPlugin from './plugins/ActionsPlugin'; +import AutocompletePlugin from './plugins/AutocompletePlugin'; +import AutoEmbedPlugin from './plugins/AutoEmbedPlugin'; +import AutoLinkPlugin from './plugins/AutoLinkPlugin'; +import CodeActionMenuPlugin from './plugins/CodeActionMenuPlugin'; +import CodeHighlightPlugin from './plugins/CodeHighlightPlugin'; +import CollapsiblePlugin from './plugins/CollapsiblePlugin'; +import CommentPlugin from './plugins/CommentPlugin'; +import ComponentPickerPlugin from './plugins/ComponentPickerPlugin'; +import ContextMenuPlugin from './plugins/ContextMenuPlugin'; +import DragDropPaste from './plugins/DragDropPastePlugin'; +import DraggableBlockPlugin from './plugins/DraggableBlockPlugin'; +import EmojiPickerPlugin from './plugins/EmojiPickerPlugin'; +import EmojisPlugin from './plugins/EmojisPlugin'; +import EquationsPlugin from './plugins/EquationsPlugin'; +import ExcalidrawPlugin from './plugins/ExcalidrawPlugin'; +import FigmaPlugin from './plugins/FigmaPlugin'; +import FloatingLinkEditorPlugin from './plugins/FloatingLinkEditorPlugin'; +import FloatingTextFormatToolbarPlugin from './plugins/FloatingTextFormatToolbarPlugin'; +import ImagesPlugin from './plugins/ImagesPlugin'; +import InlineImagePlugin from './plugins/InlineImagePlugin'; +import KeywordsPlugin from './plugins/KeywordsPlugin'; +import {LayoutPlugin} from './plugins/LayoutPlugin/LayoutPlugin'; +import LinkPlugin from './plugins/LinkPlugin'; +import ListMaxIndentLevelPlugin from './plugins/ListMaxIndentLevelPlugin'; +import MarkdownShortcutPlugin from './plugins/MarkdownShortcutPlugin'; +import {MaxLengthPlugin} from './plugins/MaxLengthPlugin'; +import MentionsPlugin from './plugins/MentionsPlugin'; +import PageBreakPlugin from './plugins/PageBreakPlugin'; +import PollPlugin from './plugins/PollPlugin'; +import SpeechToTextPlugin from './plugins/SpeechToTextPlugin'; +import TabFocusPlugin from './plugins/TabFocusPlugin'; +import TableCellActionMenuPlugin from './plugins/TableActionMenuPlugin'; +import TableCellResizer from './plugins/TableCellResizer'; +import TableOfContentsPlugin from './plugins/TableOfContentsPlugin'; +import ToolbarPlugin from './plugins/ToolbarPlugin'; +import TreeViewPlugin from './plugins/TreeViewPlugin'; +import TwitterPlugin from './plugins/TwitterPlugin'; +import YouTubePlugin from './plugins/YouTubePlugin'; +import ContentEditable from './ui/ContentEditable'; +import Placeholder from './ui/Placeholder'; + +import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; +import GlobalEventsPlugin from "./plugins/GlobalEventsPlugin"; + +const skipCollaborationInit = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + window.parent != null && window.parent.frames.right === window; + + +function OnChangePlugin({ onChange }) { + const [editor] = useLexicalComposerContext(); + useEffect(() => { + return editor.registerUpdateListener(({editorState}) => { + onChange(editorState); + }); + }, [editor, onChange]); +} + +export default function Editor(props: { noteId: bigint; noteTitle: string; editData: any; handleLawClick_item2: any; }): JSX.Element { + const [editor] = useLexicalComposerContext(); + + console.log(props.noteId) + + const {historyState} = useSharedHistoryContext(); + const { + settings: { + isCollab, + isAutocomplete, + isMaxLength, + isCharLimit, + isCharLimitUtf8, + isRichText, + showTreeView, + showTableOfContents, + shouldUseLexicalContextMenu, + tableCellMerge, + tableCellBackgroundColor, + }, + } = useSettings(); + const isEditable = useLexicalEditable(); + const text = isCollab + ? 'Enter some collaborative rich text...' + : isRichText + ? 'Enter some rich text...' + : 'Enter some plain text...'; + const placeholder = {text}; + const [floatingAnchorElem, setFloatingAnchorElem] = + useState(null); + const [isSmallWidthViewport, setIsSmallWidthViewport] = + useState(false); + const [isLinkEditMode, setIsLinkEditMode] = useState(false); + const [editorState, setEditorState] = useState(); + const onRef = (_floatingAnchorElem: HTMLDivElement) => { + if (_floatingAnchorElem !== null) { + setFloatingAnchorElem(_floatingAnchorElem); + } + }; + + useEffect(() => { + const updateViewPortWidth = () => { + const isNextSmallWidthViewport = + CAN_USE_DOM && window.matchMedia('(max-width: 1025px)').matches; + + if (isNextSmallWidthViewport !== isSmallWidthViewport) { + setIsSmallWidthViewport(isNextSmallWidthViewport); + } + }; + updateViewPortWidth(); + window.addEventListener('resize', updateViewPortWidth); + + return () => { + window.removeEventListener('resize', updateViewPortWidth); + }; + }, [isSmallWidthViewport]); + + + function onChange(editorState) { + // Call toJSON on the EditorState object, which produces a serialization safe string + const editorStateJSON = editorState.toJSON(); + + // However, we still have a JavaScript object, so we need to convert it to an actual string with JSON.stringify + setEditorState(JSON.stringify(editorStateJSON)); + + } + + console.log(props.noteId) + // console.log(props.editData) + + return ( + <> + {isRichText && } +
+ {isMaxLength && } + + + + + + + + + + + + + + + {isRichText ? ( + <> + {isCollab ? ( + + ) : ( + + )} + +
+ +
+
+ } + placeholder={placeholder} + ErrorBoundary={LexicalErrorBoundary} + /> + + + + + + + + + + + + + + + {!isEditable && } + + + + + + + + + + + {floatingAnchorElem && !isSmallWidthViewport && ( + <> + + + + + + + )} + + ) : ( + <> + } + placeholder={placeholder} + ErrorBoundary={LexicalErrorBoundary} + /> + + + )} + {(isCharLimit || isCharLimitUtf8) && ( + + )} + {isAutocomplete && } +
{showTableOfContents && }
+ {shouldUseLexicalContextMenu && } + + + {showTreeView && } + + ); +} diff --git a/cyynote-frontend/src/components/lexical/LexicalApp.tsx b/cyynote-frontend/src/components/lexical/LexicalApp.tsx new file mode 100644 index 0000000..d0d14b0 --- /dev/null +++ b/cyynote-frontend/src/components/lexical/LexicalApp.tsx @@ -0,0 +1,210 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +import "./index.css"; +import {$createLinkNode} from '@lexical/link'; +import {$createListItemNode, $createListNode} from '@lexical/list'; +import {LexicalComposer} from '@lexical/react/LexicalComposer'; +import {$createHeadingNode, $createQuoteNode} from '@lexical/rich-text'; +import {$createParagraphNode, $createTextNode, $getRoot} from 'lexical'; + +import {isDevPlayground} from './appSettings'; +import {SettingsContext, useSettings} from './context/SettingsContext'; +import {SharedAutocompleteContext} from './context/SharedAutocompleteContext'; +import {SharedHistoryContext} from './context/SharedHistoryContext'; +import Editor from './Editor'; +import logo from './images/logo.svg'; +import PlaygroundNodes from './nodes/PlaygroundNodes'; +import DocsPlugin from './plugins/DocsPlugin'; +import PasteLogPlugin from './plugins/PasteLogPlugin'; +import {TableContext} from './plugins/TablePlugin'; +import TestRecorderPlugin from './plugins/TestRecorderPlugin'; +import TypingPerfPlugin from './plugins/TypingPerfPlugin'; +import Settings from './Settings'; +import PlaygroundEditorTheme from './themes/PlaygroundEditorTheme'; + +console.warn( + 'If you are profiling the playground app, please ensure you turn off the debug view. You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.', +); + +function prepopulatedRichText() { + const root = $getRoot(); + if (root.getFirstChild() === null) { + const heading = $createHeadingNode('h1'); + heading.append($createTextNode('Welcome to the playground')); + root.append(heading); + const quote = $createQuoteNode(); + quote.append( + $createTextNode( + `In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of the editor. ` + + `You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.`, + ), + ); + root.append(quote); + const paragraph = $createParagraphNode(); + paragraph.append( + $createTextNode('The playground is a demo environment built with '), + $createTextNode('@lexical/react').toggleFormat('code'), + $createTextNode('.'), + $createTextNode(' Try typing in '), + $createTextNode('some text').toggleFormat('bold'), + $createTextNode(' with '), + $createTextNode('different').toggleFormat('italic'), + $createTextNode(' formats.'), + ); + root.append(paragraph); + const paragraph2 = $createParagraphNode(); + paragraph2.append( + $createTextNode( + 'Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!', + ), + ); + root.append(paragraph2); + const paragraph3 = $createParagraphNode(); + paragraph3.append( + $createTextNode(`If you'd like to find out more about Lexical, you can:`), + ); + root.append(paragraph3); + const list = $createListNode('bullet'); + list.append( + $createListItemNode().append( + $createTextNode(`Visit the `), + $createLinkNode('https://lexical.dev/').append( + $createTextNode('Lexical website'), + ), + $createTextNode(` for documentation and more information.`), + ), + $createListItemNode().append( + $createTextNode(`Check out the code on our `), + $createLinkNode('https://github.com/facebook/lexical').append( + $createTextNode('GitHub repository'), + ), + $createTextNode(`.`), + ), + $createListItemNode().append( + $createTextNode(`Playground code can be found `), + $createLinkNode( + 'https://github.com/facebook/lexical/tree/main/packages/lexical-playground', + ).append($createTextNode('here')), + $createTextNode(`.`), + ), + $createListItemNode().append( + $createTextNode(`Join our `), + $createLinkNode('https://discord.com/invite/KmG4wQnnD9').append( + $createTextNode('Discord Server'), + ), + $createTextNode(` and chat with the team.`), + ), + ); + root.append(list); + const paragraph4 = $createParagraphNode(); + paragraph4.append( + $createTextNode( + `Lastly, we're constantly adding cool new features to this playground. So make sure you check back here when you next get a chance :).`, + ), + ); + root.append(paragraph4); + } +} + +function LexicalApp(props: { noteId: bigint; editData: object | null; noteTitle: string; handleLawClick_item1: any; }): JSX.Element { + const { + settings: {isCollab, emptyEditor, measureTypingPerf}, + } = useSettings(); + + console.log(props.noteId) + // console.log(props.editData) + console.log(props.editData!==null) + console.log(prepopulatedRichText) + + const initialConfig = { + // editorState: isCollab + // ? null + // : emptyEditor + // ? undefined + // : prepopulatedRichText, + editorState: props.editData!==null?props.editData:prepopulatedRichText, + namespace: 'Playground', + nodes: [...PlaygroundNodes], + onError: (error: Error) => { + throw error; + }, + theme: PlaygroundEditorTheme, + }; + + return ( + + + + + {/*
*/} + {/* */} + {/* Lexical Logo*/} + {/* */} + {/*
*/} +
+ +
+ {/**/} + {/*{isDevPlayground ? : null}*/} + {/*{isDevPlayground ? : null}*/} + {/*{isDevPlayground ? : null}*/} + + {measureTypingPerf ? : null} +
+
+
+
+ ); +} + +export default function PlaygroundApp(props: { noteId: bigint; noteTitle: string; editData: object; handleLawClick: any; }): JSX.Element { + + return ( + // + + + // + // + // + // + // + // + // + ); +} diff --git a/cyynote-frontend/src/components/lexical/Settings.tsx b/cyynote-frontend/src/components/lexical/Settings.tsx new file mode 100644 index 0000000..1d05cb6 --- /dev/null +++ b/cyynote-frontend/src/components/lexical/Settings.tsx @@ -0,0 +1,150 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import * as React from 'react'; +import {useMemo, useState} from 'react'; + +import {isDevPlayground} from './appSettings'; +import {useSettings} from './context/SettingsContext'; +import Switch from './ui/Switch'; + +export default function Settings(): JSX.Element { + const windowLocation = window.location; + const { + setOption, + settings: { + measureTypingPerf, + isCollab, + isRichText, + isMaxLength, + isCharLimit, + isCharLimitUtf8, + isAutocomplete, + showTreeView, + showNestedEditorTreeView, + disableBeforeInput, + showTableOfContents, + shouldUseLexicalContextMenu, + }, + } = useSettings(); + const [showSettings, setShowSettings] = useState(false); + const [isSplitScreen, search] = useMemo(() => { + const parentWindow = window.parent; + const _search = windowLocation.search; + const _isSplitScreen = + parentWindow && parentWindow.location.pathname === '/split/'; + return [_isSplitScreen, _search]; + }, [windowLocation]); + + return ( + <> + + )} + + ); +} diff --git a/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/ExcalidrawImage.tsx b/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/ExcalidrawImage.tsx new file mode 100644 index 0000000..65ec02f --- /dev/null +++ b/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/ExcalidrawImage.tsx @@ -0,0 +1,116 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import {exportToSvg} from '@excalidraw/excalidraw'; +import { + ExcalidrawElement, + NonDeleted, +} from '@excalidraw/excalidraw/types/element/types'; +import {AppState, BinaryFiles} from '@excalidraw/excalidraw/types/types'; +import * as React from 'react'; +import {useEffect, useState} from 'react'; + +type ImageType = 'svg' | 'canvas'; + +type Props = { + /** + * Configures the export setting for SVG/Canvas + */ + appState: AppState; + /** + * The css class applied to image to be rendered + */ + className?: string; + /** + * The Excalidraw elements to be rendered as an image + */ + elements: NonDeleted[]; + /** + * The Excalidraw elements to be rendered as an image + */ + files: BinaryFiles; + /** + * The height of the image to be rendered + */ + height?: number | null; + /** + * The ref object to be used to render the image + */ + imageContainerRef: {current: null | HTMLDivElement}; + /** + * The type of image to be rendered + */ + imageType?: ImageType; + /** + * The css class applied to the root element of this component + */ + rootClassName?: string | null; + /** + * The width of the image to be rendered + */ + width?: number | null; +}; + +// exportToSvg has fonts from excalidraw.com +// We don't want them to be used in open source +const removeStyleFromSvg_HACK = (svg: SVGElement) => { + const styleTag = svg?.firstElementChild?.firstElementChild; + + // Generated SVG is getting double-sized by height and width attributes + // We want to match the real size of the SVG element + const viewBox = svg.getAttribute('viewBox'); + if (viewBox != null) { + const viewBoxDimensions = viewBox.split(' '); + svg.setAttribute('width', viewBoxDimensions[2]); + svg.setAttribute('height', viewBoxDimensions[3]); + } + + if (styleTag && styleTag.tagName === 'style') { + styleTag.remove(); + } +}; + +/** + * @explorer-desc + * A component for rendering Excalidraw elements as a static image + */ +export default function ExcalidrawImage({ + elements, + files, + imageContainerRef, + appState, + rootClassName = null, +}: Props): JSX.Element { + const [Svg, setSvg] = useState(null); + + useEffect(() => { + const setContent = async () => { + const svg: SVGElement = await exportToSvg({ + appState, + elements, + files, + }); + removeStyleFromSvg_HACK(svg); + + svg.setAttribute('width', '100%'); + svg.setAttribute('height', '100%'); + svg.setAttribute('display', 'block'); + + setSvg(svg); + }; + setContent(); + }, [elements, files, appState]); + + return ( +
+ ); +} diff --git a/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/ExcalidrawModal.css b/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/ExcalidrawModal.css new file mode 100644 index 0000000..7438d60 --- /dev/null +++ b/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/ExcalidrawModal.css @@ -0,0 +1,62 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * + */ + +.ExcalidrawModal__overlay { + display: flex; + align-items: center; + position: fixed; + flex-direction: column; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + flex-grow: 0px; + flex-shrink: 1px; + z-index: 100; + background-color: rgba(40, 40, 40, 0.6); +} +.ExcalidrawModal__actions { + text-align: end; + position: absolute; + right: 5px; + top: 5px; + z-index: 1; +} +.ExcalidrawModal__actions button { + background-color: #fff; + border-radius: 5px; +} +.ExcalidrawModal__row { + position: relative; + padding: 40px 5px 5px; + width: 70vw; + height: 70vh; + border-radius: 8px; + box-shadow: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1), + inset 0 0 0 1px rgba(255, 255, 255, 0.5); +} +.ExcalidrawModal__row > div { + border-radius: 5px; +} +.ExcalidrawModal__modal { + position: relative; + z-index: 10; + top: 50px; + width: auto; + left: 0; + display: flex; + justify-content: center; + align-items: center; + border-radius: 8px; + background-color: #eee; +} +.ExcalidrawModal__discardModal { + margin-top: 60px; + text-align: center; +} diff --git a/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/ExcalidrawModal.tsx b/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/ExcalidrawModal.tsx new file mode 100644 index 0000000..bc6059c --- /dev/null +++ b/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/ExcalidrawModal.tsx @@ -0,0 +1,255 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import './ExcalidrawModal.css'; + +import {Excalidraw} from '@excalidraw/excalidraw'; +import { + AppState, + BinaryFiles, + ExcalidrawImperativeAPI, +} from '@excalidraw/excalidraw/types/types'; +import * as React from 'react'; +import {ReactPortal, useEffect, useLayoutEffect, useRef, useState} from 'react'; +import {createPortal} from 'react-dom'; + +import Button from '../../ui/Button'; +import Modal from '../../ui/Modal'; + +export type ExcalidrawElementFragment = { + isDeleted?: boolean; +}; + +type Props = { + closeOnClickOutside?: boolean; + /** + * The initial set of elements to draw into the scene + */ + initialElements: ReadonlyArray; + /** + * The initial set of elements to draw into the scene + */ + initialAppState: AppState; + /** + * The initial set of elements to draw into the scene + */ + initialFiles: BinaryFiles; + /** + * Controls the visibility of the modal + */ + isShown?: boolean; + /** + * Callback when closing and discarding the new changes + */ + onClose: () => void; + /** + * Completely remove Excalidraw component + */ + onDelete: () => void; + /** + * Callback when the save button is clicked + */ + onSave: ( + elements: ReadonlyArray, + appState: Partial, + files: BinaryFiles, + ) => void; +}; + +export const useCallbackRefState = () => { + const [refValue, setRefValue] = + React.useState(null); + const refCallback = React.useCallback( + (value: ExcalidrawImperativeAPI | null) => setRefValue(value), + [], + ); + return [refValue, refCallback] as const; +}; + +/** + * @explorer-desc + * A component which renders a modal with Excalidraw (a painting app) + * which can be used to export an editable image + */ +export default function ExcalidrawModal({ + closeOnClickOutside = false, + onSave, + initialElements, + initialAppState, + initialFiles, + isShown = false, + onDelete, + onClose, +}: Props): ReactPortal | null { + const excaliDrawModelRef = useRef(null); + const [excalidrawAPI, excalidrawAPIRefCallback] = useCallbackRefState(); + const [discardModalOpen, setDiscardModalOpen] = useState(false); + const [elements, setElements] = + useState>(initialElements); + const [files, setFiles] = useState(initialFiles); + + useEffect(() => { + if (excaliDrawModelRef.current !== null) { + excaliDrawModelRef.current.focus(); + } + }, []); + + useEffect(() => { + let modalOverlayElement: HTMLElement | null = null; + + const clickOutsideHandler = (event: MouseEvent) => { + const target = event.target; + if ( + excaliDrawModelRef.current !== null && + !excaliDrawModelRef.current.contains(target as Node) && + closeOnClickOutside + ) { + onDelete(); + } + }; + + if (excaliDrawModelRef.current !== null) { + modalOverlayElement = excaliDrawModelRef.current?.parentElement; + if (modalOverlayElement !== null) { + modalOverlayElement?.addEventListener('click', clickOutsideHandler); + } + } + + return () => { + if (modalOverlayElement !== null) { + modalOverlayElement?.removeEventListener('click', clickOutsideHandler); + } + }; + }, [closeOnClickOutside, onDelete]); + + useLayoutEffect(() => { + const currentModalRef = excaliDrawModelRef.current; + + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + onDelete(); + } + }; + + if (currentModalRef !== null) { + currentModalRef.addEventListener('keydown', onKeyDown); + } + + return () => { + if (currentModalRef !== null) { + currentModalRef.removeEventListener('keydown', onKeyDown); + } + }; + }, [elements, files, onDelete]); + + const save = () => { + if (elements.filter((el) => !el.isDeleted).length > 0) { + const appState = excalidrawAPI?.getAppState(); + // We only need a subset of the state + const partialState: Partial = { + exportBackground: appState.exportBackground, + exportScale: appState.exportScale, + exportWithDarkMode: appState.theme === 'dark', + isBindingEnabled: appState.isBindingEnabled, + isLoading: appState.isLoading, + name: appState.name, + theme: appState.theme, + viewBackgroundColor: appState.viewBackgroundColor, + viewModeEnabled: appState.viewModeEnabled, + zenModeEnabled: appState.zenModeEnabled, + zoom: appState.zoom, + }; + onSave(elements, partialState, files); + } else { + // delete node if the scene is clear + onDelete(); + } + }; + + const discard = () => { + if (elements.filter((el) => !el.isDeleted).length === 0) { + // delete node if the scene is clear + onDelete(); + } else { + //Otherwise, show confirmation dialog before closing + setDiscardModalOpen(true); + } + }; + + function ShowDiscardDialog(): JSX.Element { + return ( + { + setDiscardModalOpen(false); + }} + closeOnClickOutside={false}> + Are you sure you want to discard the changes? +
+ {' '} + +
+
+ ); + } + + if (isShown === false) { + return null; + } + + const onChange = ( + els: ReadonlyArray, + _: AppState, + fls: BinaryFiles, + ) => { + setElements(els); + setFiles(fls); + }; + + return createPortal( +
+
+
+ {discardModalOpen && } + +
+ + +
+
+
+
, + document.body, + ); +} diff --git a/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/index.tsx b/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/index.tsx new file mode 100644 index 0000000..6f6c20f --- /dev/null +++ b/cyynote-frontend/src/components/lexical/nodes/ExcalidrawNode/index.tsx @@ -0,0 +1,143 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { + DOMConversionMap, + DOMConversionOutput, + DOMExportOutput, + EditorConfig, + LexicalEditor, + LexicalNode, + NodeKey, + SerializedLexicalNode, + Spread, +} from 'lexical'; + +import {DecoratorNode} from 'lexical'; +import * as React from 'react'; +import {Suspense} from 'react'; + +const ExcalidrawComponent = React.lazy( + // @ts-ignore + () => import('./ExcalidrawComponent'), +); + +export type SerializedExcalidrawNode = Spread< + { + data: string; + }, + SerializedLexicalNode +>; + +function convertExcalidrawElement( + domNode: HTMLElement, +): DOMConversionOutput | null { + const excalidrawData = domNode.getAttribute('data-lexical-excalidraw-json'); + if (excalidrawData) { + const node = $createExcalidrawNode(); + node.__data = excalidrawData; + return { + node, + }; + } + return null; +} + +export class ExcalidrawNode extends DecoratorNode { + __data: string; + + static getType(): string { + return 'excalidraw'; + } + + static clone(node: ExcalidrawNode): ExcalidrawNode { + return new ExcalidrawNode(node.__data, node.__key); + } + + static importJSON(serializedNode: SerializedExcalidrawNode): ExcalidrawNode { + return new ExcalidrawNode(serializedNode.data); + } + + exportJSON(): SerializedExcalidrawNode { + return { + data: this.__data, + type: 'excalidraw', + version: 1, + }; + } + + constructor(data = '[]', key?: NodeKey) { + super(key); + this.__data = data; + } + + // View + createDOM(config: EditorConfig): HTMLElement { + const span = document.createElement('span'); + const theme = config.theme; + const className = theme.image; + if (className !== undefined) { + span.className = className; + } + return span; + } + + updateDOM(): false { + return false; + } + + static importDOM(): DOMConversionMap | null { + return { + span: (domNode: HTMLSpanElement) => { + if (!domNode.hasAttribute('data-lexical-excalidraw-json')) { + return null; + } + return { + conversion: convertExcalidrawElement, + priority: 1, + }; + }, + }; + } + + exportDOM(editor: LexicalEditor): DOMExportOutput { + const element = document.createElement('span'); + const content = editor.getElementByKey(this.getKey()); + if (content !== null) { + const svg = content.querySelector('svg'); + if (svg !== null) { + element.innerHTML = svg.outerHTML; + } + } + element.setAttribute('data-lexical-excalidraw-json', this.__data); + return {element}; + } + + setData(data: string): void { + const self = this.getWritable(); + self.__data = data; + } + + decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element { + return ( + + + + ); + } +} + +export function $createExcalidrawNode(): ExcalidrawNode { + return new ExcalidrawNode(); +} + +export function $isExcalidrawNode( + node: LexicalNode | null, +): node is ExcalidrawNode { + return node instanceof ExcalidrawNode; +} diff --git a/cyynote-frontend/src/components/lexical/nodes/FigmaNode.tsx b/cyynote-frontend/src/components/lexical/nodes/FigmaNode.tsx new file mode 100644 index 0000000..1984c4e --- /dev/null +++ b/cyynote-frontend/src/components/lexical/nodes/FigmaNode.tsx @@ -0,0 +1,135 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { + EditorConfig, + ElementFormatType, + LexicalEditor, + LexicalNode, + NodeKey, + Spread, +} from 'lexical'; + +import {BlockWithAlignableContents} from '@lexical/react/LexicalBlockWithAlignableContents'; +import { + DecoratorBlockNode, + SerializedDecoratorBlockNode, +} from '@lexical/react/LexicalDecoratorBlockNode'; +import * as React from 'react'; + +type FigmaComponentProps = Readonly<{ + className: Readonly<{ + base: string; + focus: string; + }>; + format: ElementFormatType | null; + nodeKey: NodeKey; + documentID: string; +}>; + +function FigmaComponent({ + className, + format, + nodeKey, + documentID, +}: FigmaComponentProps) { + return ( + +