Skip to content

Commit 77c0439

Browse files
authored
Merge pull request #4 from edumudu/develop
make first version of color changer
2 parents c5da39d + b84835c commit 77c0439

File tree

11 files changed

+154
-26
lines changed

11 files changed

+154
-26
lines changed

.eslintrc.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ module.exports = {
1616
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
1717
'max-len': ['error', 120],
1818
'import/prefer-default-export': 'off',
19+
'object-curly-newline': ['error', {
20+
'ObjectExpression': { 'multiline': true },
21+
'ObjectPattern': { 'multiline': true },
22+
'ImportDeclaration': 'never',
23+
'ExportDeclaration': { 'multiline': true, 'minProperties': 5 }
24+
}]
1925
},
2026
overrides: [
2127
{

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.DS_Store
22
node_modules
3-
/dist
3+
dist
44

55
# local env files
66
.env.local

public/favicon.ico

4.19 KB
Binary file not shown.

public/index.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
7+
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
8+
<title><%= htmlWebpackPlugin.options.title %></title>
9+
</head>
10+
<body>
11+
<noscript>
12+
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
13+
</noscript>
14+
<div id="app"></div>
15+
<!-- built files will be auto injected -->
16+
</body>
17+
</html>

src/assets/scss/editor/main.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
&-menu {
1010
display: flex;
11+
align-items: center;
1112
flex-wrap: wrap;
1213
padding: 15px;
1314
border-bottom: 1px solid #ddd;

src/components/editor/VEditor.vue

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:text-align="textAlign"
77
:list-type="listType"
88
@executeCommand="onExecuteCommand"
9+
@execute-command="onExecuteCommand"
910
/>
1011

1112
<v-editor-body
@@ -15,24 +16,21 @@
1516
@updateModel:textAlign="textAlign = $event"
1617
@updateModel:listType="listType = $event"
1718
/>
18-
19-
{{ fontColor }}
20-
{{ fontSize }}
21-
{{ textAlign }}
2219
</div>
2320
</template>
2421

2522
<script lang="ts">
2623
import { defineComponent, Ref, ref } from '@vue/composition-api';
2724
import VEditorHead from '@/components/editor/VEditorHead.vue';
2825
import VEditorBody from '@/components/editor/VEditorBody.vue';
29-
import { useSelection } from '@/shared/selection';
26+
import { useRange } from '@/shared/selection';
27+
import { useDOMManipulation } from '@/shared/DOMManipulation';
3028
import { CommandsItem } from '@/@types/index';
3129
32-
const useCommandsHandler = (editorBody: Ref<typeof VEditorBody>) => {
30+
const useCommandsHandler = () => {
3331
const actions: Record<string, Function> = {
3432
textAlign(el: HTMLDivElement, value: string) {
35-
const { range, moveCursorToEnd } = useSelection();
33+
const { range, moveCursorToEnd } = useRange();
3634
const common = range.commonAncestorContainer;
3735
let newElement;
3836
@@ -51,7 +49,7 @@ const useCommandsHandler = (editorBody: Ref<typeof VEditorBody>) => {
5149
},
5250
5351
list(el: HTMLDivElement, value: string) {
54-
const { range, moveCursorToEnd } = useSelection();
52+
const { range, moveCursorToEnd } = useRange();
5553
const common = range.commonAncestorContainer;
5654
5755
if (!el.contains(common) || common.isSameNode(el)) return;
@@ -80,12 +78,55 @@ const useCommandsHandler = (editorBody: Ref<typeof VEditorBody>) => {
8078
el.replaceChild(newEl, myNode);
8179
moveCursorToEnd(newEl);
8280
},
81+
82+
color(el: HTMLDivElement, value: string): void {
83+
const { range, setRange, moveCursorToEnd } = useRange();
84+
const { getEndNode } = useDOMManipulation();
85+
const { startContainer, startOffset, endOffset, endContainer } = range as Range & { startContainer: Text };
86+
const endSpan = document.createElement('span');
87+
const startSpan = document.createElement('span');
88+
const startTextNode = document.createTextNode(startContainer.textContent?.slice(startOffset) || '\u200B');
89+
const endTextNode = document.createTextNode('');
90+
91+
if (!el.contains(startContainer)) return moveCursorToEnd(el.lastChild || el);
92+
93+
startSpan.style.color = value;
94+
endSpan.style.color = value;
95+
startSpan.appendChild(startTextNode);
96+
startContainer.replaceData(startOffset, -1, '');
97+
startContainer.after(startSpan);
98+
99+
if (startContainer.isSameNode(endContainer)) return setRange({ node: startTextNode }, { node: startTextNode });
100+
101+
const endNode = getEndNode(startTextNode, endContainer, (n) => {
102+
const sibling = (n.nodeType === 3 ? n.parentElement : n) as HTMLElement;
103+
sibling.style.color = value;
104+
});
105+
106+
let textEndNode = (endNode.nodeType === 3 ? endNode : endNode.lastChild) as Text | null;
107+
108+
if (!textEndNode) {
109+
textEndNode = document.createTextNode('');
110+
endNode.appendChild(textEndNode);
111+
}
112+
113+
endTextNode.textContent = (textEndNode.textContent || '').slice(0, endOffset);
114+
endSpan.appendChild(endTextNode);
115+
textEndNode.replaceData(0, endOffset, '');
116+
117+
if (endNode.isSameNode(textEndNode)) {
118+
const parentEndNode = endNode.parentNode as Node;
119+
parentEndNode.insertBefore(endSpan, textEndNode);
120+
} else {
121+
endNode.insertBefore(endSpan, textEndNode);
122+
}
123+
124+
return setRange({ node: startTextNode }, { node: endTextNode });
125+
},
83126
};
84127
85128
const onExecuteCommand = (rule: CommandsItem) => {
86-
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
87-
// @ts-ignore
88-
actions[rule.command](editorBody.value.$data.textContainer, rule.value);
129+
actions[rule.command](document.querySelector('#text-editor'), rule.value);
89130
};
90131
91132
return { onExecuteCommand };
@@ -113,10 +154,8 @@ export default defineComponent({
113154
114155
setup() {
115156
const editorBody = ref(VEditorBody);
116-
const { onExecuteCommand } = useCommandsHandler(editorBody);
117-
const {
118-
fontSize, fontColor, textAlign, listType,
119-
} = useProepertiesControll();
157+
const { onExecuteCommand } = useCommandsHandler();
158+
const { fontSize, fontColor, textAlign, listType } = useProepertiesControll();
120159
121160
return {
122161
fontSize,

src/components/editor/VEditorBody.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
SetupContext,
1717
} from '@vue/composition-api';
1818
import { useSelection } from '@/shared/selection';
19+
import { rgbToHex } from '@/shared/color';
1920
2021
const useSelectionHandle = (emit: SetupContext['emit']) => {
2122
const textContainer = ref(document.createElement('div'));
@@ -31,7 +32,7 @@ const useSelectionHandle = (emit: SetupContext['emit']) => {
3132
return;
3233
}
3334
34-
emit('updateModel:fontColor', getStyle(element, 'color'));
35+
emit('updateModel:fontColor', rgbToHex(getStyle(element, 'color')));
3536
emit('updateModel:fontSize', getStyle(element, 'font-size'));
3637
emit('updateModel:textAlign', getStyle(element, 'text-align'));
3738

src/components/editor/VEditorHead.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
:selected="listType"
1212
@changeCommand="onChangeCommand"
1313
/>
14+
15+
<input
16+
type="color"
17+
:value="fontColor"
18+
@change="e => $emit('execute-command', { command: 'color', value: e.target.value })"
19+
/>
1420
</div>
1521
</template>
1622

@@ -40,6 +46,11 @@ export default defineComponent({
4046
type: String,
4147
required: true,
4248
},
49+
50+
fontColor: {
51+
type: String,
52+
required: true,
53+
},
4354
},
4455
4556
setup(props, { emit }) {

src/shared/DOMManipulation.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
type NodeText = Node | Text;
2+
type WrapCallback = (node: NodeText) => void;
3+
4+
export const useDOMManipulation = () => {
5+
const getEndNode = (n: NodeText, endNode: NodeText = document, wrapCallback: WrapCallback | null): NodeText => {
6+
if (n.isSameNode(endNode)) return n;
7+
if (n.contains(endNode)) return getEndNode(n.firstChild as Node, endNode, wrapCallback);
8+
if (n.nextSibling?.contains(endNode)) return getEndNode(n.nextSibling, endNode, wrapCallback);
9+
10+
if (n.nextSibling && wrapCallback) {
11+
wrapCallback(n.nextSibling);
12+
13+
return getEndNode(n.nextSibling, endNode, wrapCallback);
14+
}
15+
16+
return getEndNode(n.parentNode as NodeText, endNode, wrapCallback);
17+
};
18+
19+
return { getEndNode };
20+
};

src/shared/color.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* eslint-disable no-bitwise */
2+
const rgbToHexUnique = (rgb: string): string => {
3+
let hex = Number(rgb).toString(16);
4+
if (hex.length < 2) {
5+
hex = `0${hex}`;
6+
}
7+
return hex;
8+
};
9+
10+
export const rgbToHex = (rgb: string): string => {
11+
const [rgbValue] = rgb.match(/\(.*?\)/) || '';
12+
const [r, g, b] = rgbValue.replace(/(\(|\))/g, '').split(', ');
13+
14+
const red = rgbToHexUnique(r);
15+
const green = rgbToHexUnique(g);
16+
const blue = rgbToHexUnique(b);
17+
18+
return `#${red + green + blue}`;
19+
};

0 commit comments

Comments
 (0)