Skip to content

Commit

Permalink
feat(textchecker-element): implement LintEngine API
Browse files Browse the repository at this point in the history
  • Loading branch information
azu committed Aug 2, 2020
1 parent 22fd569 commit 8cd5b1a
Show file tree
Hide file tree
Showing 13 changed files with 345 additions and 76 deletions.
1 change: 1 addition & 0 deletions packages/@textlint/compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"dependencies": {
"@textlint/config-loader": "^0.3.0",
"@textlint/kernel": "^3.3.6",
"@textlint/types": "^1.4.5",
"@textlint/runtime-helper": "^0.3.0",
"meow": "^7.0.1",
"rimraf": "^3.0.2",
Expand Down
14 changes: 14 additions & 0 deletions packages/@textlint/compiler/src/CodeGenerator/API.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { TextlintFixResult, TextlintMessage, TextlintResult } from "@textlint/types";

/**
* textlint Server API
*/
export type API = {
lint({ text, ext }: { text: string; ext: string }): TextlintResult[];
// fix all text with all rule
fixAll({ text, ext }: { text: string; ext: string }): TextlintFixResult;
// fix all with with a rule
fixRule({ text, ext, message }: { text: string; ext: string; message: TextlintMessage }): TextlintFixResult;
// fix the text
fixText({ text, ext, message }: { text: string; ext: string; message: TextlintMessage }): { output: string };
};
34 changes: 26 additions & 8 deletions packages/textchecker-element/public/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { attachToTextArea, AttachTextAreaParams } from "../src/index";
import { attachToTextArea } from "../src/index";
import type { TextlintFixResult, TextlintMessage, TextlintResult } from "@textlint/types";
import type {
TextlintWorkerCommandFix,
TextlintWorkerCommandLint,
TextlintWorkerCommandResponse
} from "@textlint/compiler";
import { LintEngineAPI } from "../src/attach-to-text-area";

const statusElement = document.querySelector("#js-status");
const updateStatus = (status: string) => {
Expand Down Expand Up @@ -42,15 +43,15 @@ const waiterForInit = (worker: Worker) => {
const workerStatus = waiterForInit(worker);

const createTextlint = ({ ext }: { ext: string }) => {
const lintText: AttachTextAreaParams["lintText"] = async ({ text }: { text: string }): Promise<TextlintResult> => {
const lintText: LintEngineAPI["lintText"] = async ({ text }: { text: string }): Promise<TextlintResult[]> => {
updateStatus("linting...");
return new Promise((resolve, _reject) => {
worker.addEventListener(
"message",
function (event) {
const data: TextlintWorkerCommandResponse = event.data;
if (data.command === "lint:result") {
resolve(data.result);
resolve([data.result]);
}
updateStatus("linted");
},
Expand All @@ -65,12 +66,12 @@ const createTextlint = ({ ext }: { ext: string }) => {
} as TextlintWorkerCommandLint);
});
};
const fixText: AttachTextAreaParams["fixText"] = async ({
const fixText = async ({
text,
message
}: {
text: string;
message: TextlintMessage;
message?: TextlintMessage;
}): Promise<TextlintFixResult> => {
updateStatus("fixing...");
return new Promise((resolve, _reject) => {
Expand All @@ -90,7 +91,7 @@ const createTextlint = ({ ext }: { ext: string }) => {
return worker.postMessage({
command: "fix",
text,
ruleId: message.ruleId,
ruleId: message?.ruleId,
ext: ext
} as TextlintWorkerCommandFix);
});
Expand All @@ -106,15 +107,32 @@ const createTextlint = ({ ext }: { ext: string }) => {
const targetElement = document.querySelectorAll("textarea");
const textlint = createTextlint({ ext: ".md" });
await workerStatus.ready();
const lintEngine: LintEngineAPI = {
lintText: textlint.lintText,
fixText: async ({ text, message }): Promise<{ output: string }> => {
if (!message.fix || !message.fix.range) {
return { output: text };
}
// replace fix.range[0, 1] with fix.text
return {
output: text.slice(0, message.fix.range[0]) + message.fix.text + text.slice(message.fix.range[1])
};
},
fixAll({ text }: { text: string }): Promise<TextlintFixResult> {
return textlint.fixText({ text });
},
fixRule({ text, message }: { text: string; message: TextlintMessage }): Promise<TextlintFixResult> {
return textlint.fixText({ text, message });
}
};
targetElement.forEach((element) => {
if (text) {
element.value = text;
}
attachToTextArea({
textAreaElement: element,
lintingDebounceMs: 200,
lintText: textlint.lintText,
fixText: textlint.fixText
lintEngine
});
});
})();
135 changes: 83 additions & 52 deletions packages/textchecker-element/src/attach-to-text-area.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ const createCompositionHandler = () => {
};
};

/**
* Lint Server API
*/
export type LintEngineAPI = {
lintText({ text }: { text: string }): Promise<TextlintResult[]>;
// fix all text with all rule
fixAll({ text }: { text: string }): Promise<TextlintFixResult>;
// fix all with with a rule
fixRule({ text, message }: { text: string; message: TextlintMessage }): Promise<TextlintFixResult>;
// fix the text
fixText({ text, message }: { text: string; message: TextlintMessage }): Promise<{ output: string }>;
};

export type AttachTextAreaParams = {
/**
* target textarea element
Expand All @@ -28,15 +41,14 @@ export type AttachTextAreaParams = {
* default: 200ms
*/
lintingDebounceMs: number;
// process
lintText: ({ text }: { text: string }) => Promise<TextlintResult>;
fixText: ({ text, message }: { text: string; message: TextlintMessage }) => Promise<TextlintFixResult>;
// user should implement LintEngineAPI and pass it
lintEngine: LintEngineAPI;
};

/**
* Attach text-checker component to `<textarea>` element
*/
export const attachToTextArea = ({ textAreaElement, lintingDebounceMs, lintText, fixText }: AttachTextAreaParams) => {
export const attachToTextArea = ({ textAreaElement, lintingDebounceMs, lintEngine }: AttachTextAreaParams) => {
const textChecker = new TextCheckerElement({
targetElement: textAreaElement,
hoverPadding: 10
Expand All @@ -52,58 +64,77 @@ export const attachToTextArea = ({ textAreaElement, lintingDebounceMs, lintText,
return;
}
const text = textAreaElement.value;
const result = await lintText({
const results = await lintEngine.lintText({
text
});
const annotations = result.messages.map((message) => {
const card: TextCheckerCard = {
id: message.ruleId + "::" + message.index,
message: message.message,
fixable: Boolean(message.fix)
};
return {
start: message.index,
end: message.index + 1,
onMouseEnter: ({ rectItem }: { rectItem: TextCheckerElementRectItem }) => {
textCheckerPopup.updateCard({
card: card,
rect: {
top:
rectItem.boxBorderWidth +
rectItem.boxMarginTop +
rectItem.boxPaddingTop +
rectItem.boxAbsoluteY +
rectItem.top +
rectItem.height,
left: rectItem.boxAbsoluteX + rectItem.left,
width: rectItem.width
},
handlers: {
async onFixIt() {
const currentText = text;
const fixResult = await fixText({
text,
message
});
if (currentText === text && currentText !== fixResult.output) {
textAreaElement.value = fixResult.output;
await update();
textCheckerPopup.dismissCard(card);
}
},
onIgnore() {
console.log("onIgnore");
const updateText = async (newText: string, card: TextCheckerCard) => {
const currentText = textAreaElement.value;
if (currentText === text && currentText !== newText) {
textAreaElement.value = newText;
await update();
textCheckerPopup.dismissCard(card);
}
};
const annotations = results.flatMap((result) => {
return result.messages.map((message) => {
const card: TextCheckerCard = {
id: message.ruleId + "::" + message.index,
message: message.message,
messageRuleId: message.ruleId,
fixable: Boolean(message.fix)
};
return {
start: message.index,
end: message.index + 1,
onMouseEnter: ({ rectItem }: { rectItem: TextCheckerElementRectItem }) => {
textCheckerPopup.updateCard({
card: card,
rect: {
top:
rectItem.boxBorderWidth +
rectItem.boxMarginTop +
rectItem.boxPaddingTop +
rectItem.boxAbsoluteY +
rectItem.top +
rectItem.height,
left: rectItem.boxAbsoluteX + rectItem.left,
width: rectItem.width
},
onSeeDocument() {
console.log("onSeeDocument");
handlers: {
async onFixText() {
const fixResults = await lintEngine.fixText({
text,
message
});
await updateText(fixResults.output, card);
},
async onFixAll() {
const fixResults = await lintEngine.fixAll({
text
});
await updateText(fixResults.output, card);
},
async onFixRule() {
const fixResults = await lintEngine.fixRule({
text,
message
});
await updateText(fixResults.output, card);
},
onIgnore() {
console.log("onIgnore");
},
onSeeDocument() {
console.log("onSeeDocument");
}
}
}
});
},
onMouseLeave() {
textCheckerPopup.dismissCard(card);
}
};
});
},
onMouseLeave() {
textCheckerPopup.dismissCard(card);
}
};
});
});
textChecker.updateAnnotations(annotations);
}, lintingDebounceMs);
Expand Down
47 changes: 45 additions & 2 deletions packages/textchecker-element/src/text-checker-popup-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type TextCheckerPopupElementAttributes = {
export type TextCheckerCard = {
id: string;
message: string;
messageRuleId: string;
fixable: boolean;
};
export type TextCheckerCardRect = {
Expand All @@ -17,7 +18,9 @@ export type TextCheckerCardRect = {
};

export type TextCheerHandlers = {
onFixIt?: () => void;
onFixText?: () => void;
onFixAll?: () => void;
onFixRule?: () => void;
onIgnore?: () => void;
onSeeDocument?: () => void;
};
Expand Down Expand Up @@ -169,7 +172,7 @@ export class TextCheckerPopupElement extends HTMLElement {
? {
message: firstLine,
label: "Fix it!",
onClick: state.handlers?.onFixIt,
onClick: state.handlers?.onFixText,
icon: html`<svg
class="popup-listItem--icon"
width="24"
Expand All @@ -186,6 +189,46 @@ export class TextCheckerPopupElement extends HTMLElement {
: {
message: state.card.message
},
...(state.card.fixable
? [
{
label: `Fix this rule: ${state.card.messageRuleId} problems`,
onClick: state.handlers?.onFixRule,
icon: html`<svg
class="popup-listItem--icon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.5 5.6L5 7L6.4 4.5L5 2L7.5 3.4L10 2L8.6 4.5L10 7L7.5 5.6ZM19.5 15.4L22 14L20.6 16.5L22 19L19.5 17.6L17 19L18.4 16.5L17 14L19.5 15.4ZM22 2L20.6 4.5L22 7L19.5 5.6L17 7L18.4 4.5L17 2L19.5 3.4L22 2ZM13.34 12.78L15.78 10.34L13.66 8.22L11.22 10.66L13.34 12.78ZM14.37 7.29L16.71 9.63C17.1 10 17.1 10.65 16.71 11.04L5.04 22.71C4.65 23.1 4 23.1 3.63 22.71L1.29 20.37C0.899998 20 0.899998 19.35 1.29 18.96L12.96 7.29C13.35 6.9 14 6.9 14.37 7.29Z"
/>
</svg>`
}
]
: []),
...(state.card.fixable
? [
{
label: `Fix all problems`,
onClick: state.handlers?.onFixAll,
icon: html`<svg
class="popup-listItem--icon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.5 5.6L5 7L6.4 4.5L5 2L7.5 3.4L10 2L8.6 4.5L10 7L7.5 5.6ZM19.5 15.4L22 14L20.6 16.5L22 19L19.5 17.6L17 19L18.4 16.5L17 14L19.5 15.4ZM22 2L20.6 4.5L22 7L19.5 5.6L17 7L18.4 4.5L17 2L19.5 3.4L22 2ZM13.34 12.78L15.78 10.34L13.66 8.22L11.22 10.66L13.34 12.78ZM14.37 7.29L16.71 9.63C17.1 10 17.1 10.65 16.71 11.04L5.04 22.71C4.65 23.1 4 23.1 3.63 22.71L1.29 20.37C0.899998 20 0.899998 19.35 1.29 18.96L12.96 7.29C13.35 6.9 14 6.9 14.37 7.29Z"
/>
</svg>`
}
]
: []),
{
label: "Ignore",
onClick: state.handlers?.onIgnore,
Expand Down
2 changes: 2 additions & 0 deletions packages/webextension/app/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"activeTab",
"contextMenus",
"downloads",
"webRequest",
"webRequestBlocking",
"tabs",
"<all_urls>"
]
Expand Down
14 changes: 14 additions & 0 deletions packages/webextension/app/pages/install-dialog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Install Dialog</title>
</head>
<body>
<h1>Install Page</h1>
<div>
<button id="js-install-button">Install</button>
</div>
<script src="../scripts/install-dialog.js"></script>
</body>
</html>
Loading

0 comments on commit 8cd5b1a

Please sign in to comment.