您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A userscript that allows you to indent & outdent blocks and auto indent for new line in the comment editor
当前为
- //
- // ==UserScript==
- // @name GitHub Indent-NewLine Comments
- // @version 0.0.1
- // @description A userscript that allows you to indent & outdent blocks and auto indent for new line in the comment editor
- // @license MIT
- // @author ly525
- // @namespace https://github.com/ly525
- // @include https://github.com/*
- // @include https://gist.github.com/*
- // @run-at document-idle
- // @grant GM_addStyle
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_registerMenuCommand
- // @connect github.com
- // @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=666427
- // @icon https://github.githubassets.com/pinned-octocat.svg
- // ==/UserScript==
- /*
- * ly525 says:
- * HEAVILY MODIFIED 3/8/2020 from https://greasyfork.org/zh-CN/scripts/28176-github-indent-comments
- * Thanks a lot!
- */
- /* HEAVILY MODIFIED 3/17/2017 from https://github.com/timdown/rangyinputs
- * - The code was unwrapped
- * - jQuery elements removed, updated to ES2015
- * - Unneeded code removed
- * - Added global variable "rangyInput"
- */
- /**
- * @license Rangy Inputs, a jQuery plug-in for selection and caret manipulation
- * within textareas and text inputs.
- *
- * https://github.com/timdown/rangyinputs
- *
- * For range and selection features for contenteditable, see Rangy.
- * http://code.google.com/p/rangy/
- *
- * xxxx Depends on jQuery 1.0 or later. xxxxx
- *
- * Copyright 2014, Tim Down
- * Licensed under the MIT license.
- * Version: 1.2.0
- * Build date: 30 November 2014
- */
- /* jshint esnext:true */
- (() => {
- window.rangyInput = {};
- const UNDEF = "undefined";
- let getSelection, setSelection;
- // Trio of isHost* functions taken from Peter Michaux's article:
- // State of the art browser scripting (https://goo.gl/w6HPyE)
- function isHostMethod(object, property) {
- var t = typeof object[property];
- return t === "function" ||
- (!!(t == "object" && object[property])) ||
- t == "unknown";
- }
- function isHostProperty(object, property) {
- return typeof(object[property]) != UNDEF;
- }
- function isHostObject(object, property) {
- return !!(typeof(object[property]) == "object" && object[property]);
- }
- function fail(reason) {
- if (window.console && window.console.log) {
- window.console.log(
- `RangyInputs not supported in your browser. Reason: ${reason}`
- );
- }
- }
- function adjustOffsets(el, start, end) {
- if (start < 0) {
- start += el.value.length;
- }
- if (typeof end == UNDEF) {
- end = start;
- }
- if (end < 0) {
- end += el.value.length;
- }
- return { start: start, end: end };
- }
- function makeSelection(el, start, end) {
- return {
- start: start,
- end: end,
- length: end - start,
- text: el.value.slice(start, end)
- };
- }
- function getBody() {
- return isHostObject(document, "body") ?
- document.body :
- document.querySelector("body");
- }
- window.rangyInput.init = () => {
- const testTextArea = document.createElement("textarea");
- getBody().appendChild(testTextArea);
- if (
- isHostProperty(testTextArea, "selectionStart") &&
- isHostProperty(testTextArea, "selectionEnd")
- ) {
- getSelection = el => {
- return makeSelection(el, el.selectionStart, el.selectionEnd);
- };
- setSelection = (el, startOffset, endOffset) => {
- var offsets = adjustOffsets(el, startOffset, endOffset);
- el.selectionStart = offsets.start;
- el.selectionEnd = offsets.end;
- };
- } else if (
- isHostMethod(testTextArea, "createTextRange") &&
- isHostObject(document, "selection") &&
- isHostMethod(document.selection, "createRange")
- ) {
- getSelection = el => {
- let normalizedValue, textInputRange, len, endRange,
- start = 0,
- end = 0;
- const range = document.selection.createRange();
- if (range && range.parentElement() == el) {
- len = el.value.length;
- normalizedValue = el.value.replace(/\r\n/g, "\n");
- textInputRange = el.createTextRange();
- textInputRange.moveToBookmark(range.getBookmark());
- endRange = el.createTextRange();
- endRange.collapse(false);
- if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
- start = end = len;
- } else {
- start = -textInputRange.moveStart("character", -len);
- start += normalizedValue.slice(0, start).split("\n").length - 1;
- if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
- end = len;
- } else {
- end = -textInputRange.moveEnd("character", -len);
- end += normalizedValue.slice(0, end).split("\n").length - 1;
- }
- }
- }
- return makeSelection(el, start, end);
- };
- // Moving across a line break only counts as moving one character in a
- // TextRange, whereas a line break in the textarea value is two
- // characters. This function corrects for that by converting a text offset
- // into a range character offset by subtracting one character for every
- // line break in the textarea prior to the offset
- const offsetToRangeCharacterMove = function(el, offset) {
- return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
- };
- setSelection = (el, startOffset, endOffset) => {
- const offsets = adjustOffsets(el, startOffset, endOffset),
- range = el.createTextRange(),
- startCharMove = offsetToRangeCharacterMove(el, offsets.start);
- range.collapse(true);
- if (offsets.start == offsets.end) {
- range.move("character", startCharMove);
- } else {
- range.moveEnd(
- "character",
- offsetToRangeCharacterMove(el, offsets.end)
- );
- range.moveStart("character", startCharMove);
- }
- range.select();
- };
- } else {
- getBody().removeChild(testTextArea);
- fail("No means of finding text input caret position");
- return;
- }
- // Clean up
- getBody().removeChild(testTextArea);
- function getValueAfterPaste(el, text) {
- const val = el.value,
- sel = getSelection(el),
- selStart = sel.start;
- return {
- value: val.slice(0, selStart) + text + val.slice(sel.end),
- index: selStart,
- replaced: sel.text
- };
- }
- function pasteTextWithCommand(el, text) {
- el.focus();
- const sel = getSelection(el);
- // Hack to work around incorrect delete command when deleting the last
- // word on a line
- setSelection(el, sel.start, sel.end);
- if (text === "") {
- document.execCommand("delete", false, null);
- } else {
- document.execCommand("insertText", false, text);
- }
- return {
- replaced: sel.text,
- index: sel.start
- };
- }
- function pasteTextWithValueChange(el, text) {
- el.focus();
- const valueAfterPaste = getValueAfterPaste(el, text);
- el.value = valueAfterPaste.value;
- return valueAfterPaste;
- }
- let pasteText = (el, text) => {
- const valueAfterPaste = getValueAfterPaste(el, text);
- try {
- const pasteInfo = pasteTextWithCommand(el, text);
- if (el.value == valueAfterPaste.value) {
- pasteText = pasteTextWithCommand;
- return pasteInfo;
- }
- } catch (ex) {
- // Do nothing and fall back to changing the value manually
- }
- pasteText = pasteTextWithValueChange;
- el.value = valueAfterPaste.value;
- return valueAfterPaste;
- };
- function updateSelectionAfterInsert(el, startIndex, text, selBehaviour) {
- let endIndex = startIndex + text.length;
- selBehaviour = (typeof selBehaviour == "string") ?
- selBehaviour.toLowerCase() :
- "";
- if (
- (selBehaviour == "collapsetoend" || selBehaviour == "select") &&
- /[\r\n]/.test(text)
- ) {
- // Find the length of the actual text inserted, which could vary
- // depending on how the browser deals with line breaks
- const normalizedText = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
- endIndex = startIndex + normalizedText.length;
- const firstLineBreakIndex = startIndex + normalizedText.indexOf("\n");
- if (
- el.value.slice(firstLineBreakIndex, firstLineBreakIndex + 2) == "\r\n"
- ) {
- // Browser uses \r\n, so we need to account for extra \r characters
- endIndex += normalizedText.match(/\n/g).length;
- }
- }
- switch (selBehaviour) {
- case "collapsetostart":
- setSelection(el, startIndex, startIndex);
- break;
- case "collapsetoend":
- setSelection(el, endIndex, endIndex);
- break;
- case "select":
- setSelection(el, startIndex, endIndex);
- break;
- }
- }
- window.rangyInput.surroundSelectedText = (el, before, after) => {
- if (typeof after == UNDEF) {
- after = before;
- }
- const sel = getSelection(el),
- pasteInfo = pasteText(el, before + sel.text + after);
- updateSelectionAfterInsert(
- el,
- pasteInfo.index + before.length,
- sel.text,
- "select"
- );
- };
- window.rangyInput.indentSelectedText = (el, callback) => {
- const sel = getSelection(el),
- result = callback(sel.text),
- pasteInfo = pasteText(el, result);
- //「修改1」主要修改了这里,将updateSelectionAfterInsert的值由「select」改为「collapsetoend」
- updateSelectionAfterInsert(el, pasteInfo.index, result, "collapsetoend");
- };
- };
- })();
- (() => {
- "use strict";
- let spaceSize = GM_getValue("space-size", 2);
- const icons = {
- indent: `
- <svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="12" height="16" viewBox="0 0 12 16">
- <path d="M12 13c0 .6 0 1-.9 1H.9c-.9 0-.9-.4-.9-1s0-1 .9-1h10.2c.88 0 .88.4.88 1zM.92 4h10.2C12 4 12 3.6 12 3s0-1-.9-1H.92c-.9 0-.9.4-.9 1s0 1 .9 1zM11.5 7h-5C6 7 6 7.4 6 8s0 1 .5 1h5c.5 0 .5-.4.5-1s0-1-.5-1zm-7 1L0 5v6z"/>
- </svg>`,
- outdent: `
- <svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="12" height="16" viewBox="0 0 12 16">
- <path d="M12 13c0 .6 0 1-.9 1H.9c-.9 0-.9-.4-.9-1s0-1 .9-1h10.2c.88 0 .88.4.88 1zM.92 4h10.2C12 4 12 3.6 12 3s0-1-.9-1H.92c-.9 0-.9.4-.9 1s0 1 .9 1zm10.7 3H6.4c-.46 0-.4.4-.4 1s-.06 1 .4 1h5.2c.47 0 .4-.4.4-1s.07-1-.4-1zM0 8l4.5-3v6z"/>
- </svg>`
- };
- GM_addStyle(".ghio-in-outdent * { pointer-events:none; }");
- // Add indent & outdent buttons
- function addButtons() {
- createButton("Outdent");
- createButton("Indent");
- }
- function createButton(name) {
- const toolbars = $$(".toolbar-commenting"),
- nam = name.toLowerCase(),
- button = document.createElement("button");
- let el,
- indx = toolbars.length;
- if (indx) {
- button.type = "button";
- button.className = `ghio-${nam.toLowerCase()} ghio-in-outdent toolbar-item tooltipped tooltipped-n`;
- button.setAttribute("aria-label", `${name} Selected Text`);
- button.setAttribute("tabindex", "-1");
- button.innerHTML = icons[nam.toLowerCase()];
- while (indx--) {
- el = toolbars[indx];
- if (!$(`.ghio-${nam.toLowerCase()}`, el)) {
- el.insertBefore(button.cloneNode(true), el.childNodes[0]);
- }
- }
- }
- }
- function indent(text) {
- let result = [],
- block = new Array(parseInt(spaceSize, 10) + 1).join(" ");
- (text || "").split(/\r*\n/).forEach(line => {
- result.push(block + line);
- });
- return result.join("\n");
- }
- function outdent(text) {
- let regex = new RegExp(`^(\x20{1,${spaceSize}}|\xA0{1,${spaceSize}}|\x09)`),
- result = [];
- (text || "").split(/\r*\n/).forEach(line => {
- result.push(line.replace(regex, ""));
- });
- return result.join("\n");
- }
- function addBindings() {
- window.rangyInput.init();
- saveTabSize();
- $("body").addEventListener("click", event => {
- let textarea,
- target = event.target;
- if (target && target.classList.contains("ghio-in-outdent")) {
- textarea = closest(".previewable-comment-form", target);
- textarea = $(".comment-form-textarea", textarea);
- textarea.focus();
- setTimeout(() => {
- window.rangyInput.indentSelectedText(
- textarea,
- target.classList.contains("ghio-indent") ? indent : outdent
- );
- }, 100);
- return false;
- }
- });
- // Add Tab & Shift + Tab
- $("body").addEventListener("keydown", event => {
- if (event.key === "Tab") {
- let target = event.target;
- if (target && target.classList.contains("comment-form-textarea")) {
- event.preventDefault();
- target.focus();
- setTimeout(() => {
- window.rangyInput.indentSelectedText(
- target,
- // shift + tab = outdent
- event.getModifierState("Shift") ? outdent : indent
- );
- }, 100);
- }
- }
- // 「修改2」增加 New Line 自动对齐行为
- // https://stackoverflow.com/questions/5743916/how-to-add-autoindent-to-html-textarea
- else if (event.key === 'Enter') {
- let target = event.target;
- if (target && target.classList.contains("comment-form-textarea")) {
- // event.preventDefault();
- setTimeout((that) => {
- var start = that.selectionStart;
- var v = that.value;
- var thisLine = "";
- var indentation = 0;
- for (let i = start - 2; i >= 0 && v[i] != "\n"; i--) {
- thisLine = v[i] + thisLine;
- }
- for (let i = 0; i < thisLine.length && thisLine[i] == " "; i++) {
- indentation++;
- }
- that.value = v.slice(0, start) + " ".repeat(indentation) + v.slice(start);
- that.selectionStart = start + indentation;
- that.selectionEnd = start + indentation;
- }, 0.01, target);
- }
- }
- });
- }
- function saveTabSize() {
- let $el = $(".gh-indent-size");
- if (!$el) {
- $el = document.createElement("style");
- $el.setAttribute("rel", "stylesheet");
- $el.className = "gh-indent-size";
- document.querySelector("head").appendChild($el);
- }
- $el.innerHTML = `.comment-form-textarea { tab-size:${spaceSize}; }`;
- }
- function $(selector, el) {
- return (el || document).querySelector(selector);
- }
- function $$(selector, el) {
- return Array.from((el || document).querySelectorAll(selector));
- }
- function closest(selector, el) {
- while (el && el.nodeType === 1) {
- if (el.matches(selector)) {
- return el;
- }
- el = el.parentNode;
- }
- return null;
- }
- // Add GM options
- GM_registerMenuCommand(
- "Indent or outdent size",
- () => {
- const spaces = GM_getValue("indentOutdentSize", spaceSize);
- let val = prompt("Enter number of spaces to indent or outdent:", spaces);
- if (val !== null && typeof val === "string") {
- spaceSize = val;
- GM_setValue("space-size", val);
- saveTabSize();
- }
- }
- );
- document.addEventListener("ghmo:container", addButtons);
- addBindings();
- addButtons();
- })();