component.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import { useEditor } from "@/modules/editor";
  2. import { Alignment } from "@ckeditor/ckeditor5-alignment";
  3. import {
  4. Bold,
  5. Italic,
  6. Strikethrough,
  7. Underline,
  8. } from "@ckeditor/ckeditor5-basic-styles";
  9. import { InlineEditor } from "@ckeditor/ckeditor5-editor-inline";
  10. import { Essentials } from "@ckeditor/ckeditor5-essentials";
  11. import Heading from "@ckeditor/ckeditor5-heading/src/heading";
  12. import { FontColor, FontFamily, FontSize } from "@ckeditor/ckeditor5-font";
  13. import { Link } from "@ckeditor/ckeditor5-link";
  14. import { Paragraph } from "@ckeditor/ckeditor5-paragraph";
  15. import { css } from "@linaria/core";
  16. import LineHeight from "ckeditor5-line-height-latest/src/lineheight";
  17. import { nextTick } from "process";
  18. import { defineComponent, onMounted, onUnmounted, reactive, ref } from "vue";
  19. import { string } from "vue-types";
  20. import { View } from "../View";
  21. import { useCompData } from "../../defines/hook";
  22. function GetConfig() {
  23. const fontSizeOptions = [];
  24. const list = [12, 14, 16, 18, 20, 24, 28, 32, 38, 42, 46, 52, 60];
  25. for (const s of list) {
  26. fontSizeOptions.push({ title: s + "", model: s + "px" });
  27. }
  28. const fontFamilyOptions = [
  29. { title: "默认字体", model: "" },
  30. { title: "宋体", model: "宋体,Songti,STSong,serif" },
  31. { title: "黑体", model: "黑体,Heiti,STHeiti,sans-serif" },
  32. { title: "仿宋", model: "仿宋,FangSong,STFangsong,serif" },
  33. { title: "楷体", model: "楷体,KaiTi,STKaiti,sans-serif" },
  34. ];
  35. const config = {
  36. // updateSourceElementOnDestroy: true,
  37. language: "zh-cn",
  38. plugins: [
  39. Essentials,
  40. Bold,
  41. Italic,
  42. Link,
  43. Underline,
  44. Strikethrough,
  45. Heading,
  46. Paragraph,
  47. FontColor,
  48. FontSize,
  49. FontFamily,
  50. Alignment,
  51. LineHeight,
  52. ],
  53. fontSize: {
  54. options: fontSizeOptions,
  55. supportAllValues: true,
  56. },
  57. fontFamily: {
  58. options: fontFamilyOptions,
  59. supportAllValues: true,
  60. },
  61. lineHeight: {
  62. options: [1, 1.5, 2, 2.5, 3],
  63. },
  64. toolbar: {
  65. items: [
  66. // "undo",
  67. // "redo",
  68. // "|",
  69. "fontColor",
  70. "fontFamily",
  71. "fontSize",
  72. "lineHeight",
  73. "bold",
  74. "italic",
  75. "underline",
  76. "strikethrough",
  77. "|",
  78. "alignment",
  79. // "|",
  80. "link",
  81. ],
  82. },
  83. };
  84. return config;
  85. }
  86. export const Component = defineComponent({
  87. props: {
  88. compId: string().def(""),
  89. },
  90. setup(props) {
  91. const comp = useCompData<any>(props.compId);
  92. const { store, actions } = useEditor();
  93. const state = reactive({
  94. editableId: "",
  95. });
  96. if (store.isEditMode) {
  97. actions.on("textFocus", function (compId, focus) {
  98. if (compId != props.compId) return;
  99. if (focus) {
  100. state.editableId = "" + Date.now();
  101. return;
  102. }
  103. state.editableId = "";
  104. });
  105. }
  106. return () => (
  107. <View
  108. class={[textStyle]}
  109. compId={props.compId}
  110. onDblclick={() => {
  111. if (store.isEditMode) {
  112. state.editableId = "" + Date.now();
  113. }
  114. }}
  115. >
  116. {state.editableId ? (
  117. <EditorComp
  118. compId={props.compId}
  119. key={state.editableId}
  120. onLost={() => {
  121. state.editableId = "";
  122. }}
  123. />
  124. ) : (
  125. <div
  126. innerHTML={comp.value}
  127. class={[textStyle, store.isEditMode && `pointer-events-none`]}
  128. />
  129. )}
  130. </View>
  131. );
  132. },
  133. });
  134. const EditorComp = defineComponent({
  135. props: {
  136. compId: string().isRequired,
  137. },
  138. emits: ["lost"],
  139. setup(props, { emit }) {
  140. const inputRef = ref();
  141. let editorInstance = ref<InlineEditor>();
  142. const comp = useCompData<any>(props.compId);
  143. const { store, actions, helper, controls } = useEditor();
  144. let blurCanceler: any = null;
  145. onMounted(() => {
  146. blurCanceler = blurHandle();
  147. nextTick(() => {
  148. initHeight();
  149. });
  150. });
  151. onUnmounted(() => {
  152. blurCanceler?.();
  153. });
  154. const preHeight = ref<number>(0);
  155. const initHeight = () => {
  156. const h = helper.pxToDesignSize(inputRef.value?.$el.clientHeight);
  157. const isChange = Math.abs(preHeight.value - h) > 1;
  158. preHeight.value = h;
  159. actions.updateCompData(comp, "layout.size.1", preHeight.value);
  160. helper.extendStreamCard(store.currStreamCardId);
  161. };
  162. function isInCkBodyWrapper(dom: HTMLElement) {
  163. if (editorInstance.value) {
  164. const in1 =
  165. editorInstance.value.ui.view.toolbar.element?.contains(dom) ||
  166. editorInstance.value.ui.view.editable.element?.contains(dom);
  167. if (in1) return true;
  168. const ckBodyWrapper = document.querySelector(".ck-body-wrapper");
  169. if (ckBodyWrapper === dom || ckBodyWrapper?.contains(dom)) {
  170. return true;
  171. }
  172. }
  173. return false;
  174. }
  175. function blurHandle() {
  176. function blur(e: MouseEvent) {
  177. const target = e.target as HTMLElement;
  178. if (!editorInstance.value) return;
  179. if (isInCkBodyWrapper(target)) {
  180. e.stopPropagation();
  181. return;
  182. }
  183. actions.submitUpdate();
  184. emit("lost");
  185. }
  186. document.addEventListener("mousedown", blur, {
  187. capture: true,
  188. });
  189. return () => {
  190. document.removeEventListener("mousedown", blur, { capture: true });
  191. };
  192. }
  193. return () => (
  194. <ckeditor
  195. class={textStyle}
  196. ref={inputRef}
  197. editor={InlineEditor}
  198. onInput={(value: any) => {
  199. if (editorInstance.value && comp.value !== value) {
  200. actions.updateCompData(comp, "value", value);
  201. nextTick(() => {
  202. const h = helper.pxToDesignSize(inputRef.value?.$el.clientHeight);
  203. const isChange = Math.abs(preHeight.value - h) > 1;
  204. preHeight.value = h;
  205. actions.updateCompDatas(
  206. comp,
  207. ["value", "layout.size.1"],
  208. [value, preHeight.value]
  209. );
  210. helper.extendStreamCard(store.currStreamCardId);
  211. if (isChange) {
  212. console.log("changing=>", isChange);
  213. }
  214. });
  215. }
  216. }}
  217. onReady={(editor: InlineEditor) => {
  218. editorInstance.value = editor;
  219. console.log("editor");
  220. editor.setData(comp.value);
  221. editor.focus();
  222. const range = document.createRange();
  223. range.selectNodeContents(inputRef.value.$el);
  224. const selection = window.getSelection();
  225. selection?.removeAllRanges();
  226. selection?.addRange(range);
  227. }}
  228. config={GetConfig()}
  229. />
  230. );
  231. },
  232. });
  233. const textStyle = css`
  234. font-size: 14px;
  235. width: 100%;
  236. color: #666;
  237. word-break: break-word;
  238. h2 {
  239. color: #111;
  240. font-size: 24px;
  241. font-weight: 600;
  242. }
  243. h3 {
  244. font-size: 18px;
  245. color: #333;
  246. }
  247. p {
  248. margin: 0;
  249. }
  250. .ck.ck-editor__editable_inline {
  251. cursor: text;
  252. overflow: hidden;
  253. border: none !important;
  254. > :last-child,
  255. > :first-child {
  256. margin-top: 0;
  257. margin-bottom: 0;
  258. }
  259. padding: 0 !important;
  260. }
  261. `;