Compare commits
9 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f14a895fa | ||
|
|
44a5b664a0 | ||
|
|
b4cb97f6d0 | ||
|
|
d7f2fd7d09 | ||
|
|
7606bf4c06 | ||
|
|
e221723c1e | ||
|
|
b2bed26be5 | ||
|
|
1f61932393 | ||
|
|
d841acd457 |
|
|
@ -26,14 +26,14 @@ jobs:
|
||||||
npm install
|
npm install
|
||||||
wails build -clean -platform darwin/arm64 -ldflags "-X main.VersionNumber=`git describe --tags --abbrev=0`"
|
wails build -clean -platform darwin/arm64 -ldflags "-X main.VersionNumber=`git describe --tags --abbrev=0`"
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: https://lmika.dev/actions/wails-release@v1.0.2
|
uses: https://lmika.dev/actions/wails-release@v1.0.3
|
||||||
with:
|
with:
|
||||||
developer-id-cert-base64: ${{ secrets.MACOS_SIGN_P12 }}
|
developer-id-cert-base64: ${{ secrets.MACOS_SIGN_P12 }}
|
||||||
developer-id-cert-password: ${{ secrets.MACOS_SIGN_PASSWORD }}
|
developer-id-cert-password: ${{ secrets.MACOS_SIGN_PASSWORD }}
|
||||||
notarization-api-key-base64: ${{ secrets.MACOS_NOTARY_KEY }}
|
notarization-api-key-base64: ${{ secrets.MACOS_NOTARY_KEY }}
|
||||||
notarization-api-key-id: ${{ secrets.MACOS_NOTARY_KEY_ID }}
|
notarization-api-key-id: ${{ secrets.MACOS_NOTARY_KEY_ID }}
|
||||||
notarization-api-issuer-id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
|
notarization-api-issuer-id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
|
||||||
extra-build-flags: '-ldflags "-X main.VersionNumber=${{ forgejo.ref_name }}"'
|
extra-build-flags: -ldflags "-X main.VersionNumber=${{ github.ref_name }}"
|
||||||
s3-bucket: lmika-public-files
|
s3-bucket: lmika-public-files
|
||||||
s3-key: Apps/Dequoter/{version}/{filename}
|
s3-key: Apps/Dequoter/{version}/{filename}
|
||||||
s3-region: ap-southeast-2
|
s3-region: ap-southeast-2
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 539 KiB After Width: | Height: | Size: 404 KiB |
Binary file not shown.
|
|
@ -6,9 +6,12 @@
|
||||||
.editor-mountpoint {
|
.editor-mountpoint {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-bar {
|
.status-bar {
|
||||||
|
flex-shrink: 0;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
background-color: rgb(245, 245, 245);
|
background-color: rgb(245, 245, 245);
|
||||||
border-top: solid thin rgb(200, 200, 200);
|
border-top: solid thin rgb(200, 200, 200);
|
||||||
|
|
@ -142,3 +145,29 @@ dialog#command-dialog option {
|
||||||
option .option-label {
|
option .option-label {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.status-bar {
|
||||||
|
background-color: rgb(40, 44, 52);
|
||||||
|
border-top-color: rgb(70, 74, 82);
|
||||||
|
color: rgb(220, 220, 220);
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#prompt-dialog,
|
||||||
|
dialog#command-dialog {
|
||||||
|
background: rgba(40, 44, 52, 0.55);
|
||||||
|
border-color: rgba(120, 120, 120, 0.4);
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||||
|
color: rgb(220, 220, 220);
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#prompt-dialog .prompt-label {
|
||||||
|
color: rgba(255, 255, 255, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#prompt-dialog input,
|
||||||
|
dialog#command-dialog input,
|
||||||
|
dialog#command-dialog select {
|
||||||
|
color: rgb(230, 230, 230);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,17 @@ import './app.css';
|
||||||
|
|
||||||
import {basicSetup} from "codemirror";
|
import {basicSetup} from "codemirror";
|
||||||
import {EditorView, keymap} from "@codemirror/view";
|
import {EditorView, keymap} from "@codemirror/view";
|
||||||
|
import {Compartment} from "@codemirror/state";
|
||||||
|
import {oneDark} from "@codemirror/theme-one-dark";
|
||||||
import {Application} from "@hotwired/stimulus";
|
import {Application} from "@hotwired/stimulus";
|
||||||
|
|
||||||
import {textProcessor} from "./services.js";
|
import {textProcessor} from "./services.js";
|
||||||
import {multiCursorKeymap, commandPalette} from "./cmplugins.js";
|
import {multiCursorKeymap, commandPalette} from "./cmplugins.js";
|
||||||
import {indentWithTab} from "@codemirror/commands";
|
import {indentWithTab} from "@codemirror/commands";
|
||||||
|
|
||||||
|
const darkMode = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
const themeCompartment = new Compartment();
|
||||||
|
|
||||||
import {StatusController} from "./controllers/status_controller.js";
|
import {StatusController} from "./controllers/status_controller.js";
|
||||||
import {CommandsController} from "./controllers/commands_controller.js";
|
import {CommandsController} from "./controllers/commands_controller.js";
|
||||||
import {PromptController} from "./controllers/prompt_controller.js";
|
import {PromptController} from "./controllers/prompt_controller.js";
|
||||||
|
|
@ -24,9 +29,16 @@ const view = new EditorView({
|
||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
multiCursorKeymap,
|
multiCursorKeymap,
|
||||||
commandPalette,
|
commandPalette,
|
||||||
|
themeCompartment.of(darkMode.matches ? oneDark : []),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
darkMode.addEventListener("change", (e) => {
|
||||||
|
view.dispatch({
|
||||||
|
effects: themeCompartment.reconfigure(e.matches ? oneDark : []),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
window.Stimulus = Application.start()
|
window.Stimulus = Application.start()
|
||||||
Stimulus.register("commands", CommandsController);
|
Stimulus.register("commands", CommandsController);
|
||||||
Stimulus.register("prompt", PromptController);
|
Stimulus.register("prompt", PromptController);
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,19 @@ class TextProcessor {
|
||||||
setCodeMirrorEditor(editor) {
|
setCodeMirrorEditor(editor) {
|
||||||
this._editor = editor;
|
this._editor = editor;
|
||||||
window.runtime.EventsOn("process-text-response", (data) => {
|
window.runtime.EventsOn("process-text-response", (data) => {
|
||||||
|
const cursorPos = this._editor.state.selection.main.head;
|
||||||
|
const appendInsertPos = this._appendInsertPos;
|
||||||
|
this._appendInsertPos = undefined;
|
||||||
|
let newSelection;
|
||||||
const changes = data.output.map(span => {
|
const changes = data.output.map(span => {
|
||||||
if (span.append) {
|
if (span.append) {
|
||||||
|
const insertAt = appendInsertPos !== undefined ? appendInsertPos : span.pos + span.len;
|
||||||
|
if (cursorPos === insertAt) {
|
||||||
|
newSelection = { anchor: insertAt + span.text.length };
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
from: span.pos + span.len,
|
from: insertAt,
|
||||||
to: span.pos + span.len,
|
to: insertAt,
|
||||||
insert: span.text,
|
insert: span.text,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -27,7 +35,11 @@ class TextProcessor {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this._editor.dispatch({ changes: changes });
|
const transaction = { changes: changes };
|
||||||
|
if (newSelection) {
|
||||||
|
transaction.selection = newSelection;
|
||||||
|
}
|
||||||
|
this._editor.dispatch(transaction);
|
||||||
});
|
});
|
||||||
window.runtime.EventsOn("request-text-process", (data) => {
|
window.runtime.EventsOn("request-text-process", (data) => {
|
||||||
this.runTextCommand(data.action);
|
this.runTextCommand(data.action);
|
||||||
|
|
@ -44,12 +56,14 @@ class TextProcessor {
|
||||||
|
|
||||||
let inputs = [];
|
let inputs = [];
|
||||||
if (shouldBeAll) {
|
if (shouldBeAll) {
|
||||||
|
this._appendInsertPos = this._editor.state.selection.main.head;
|
||||||
inputs.push({
|
inputs.push({
|
||||||
text: this._editor.state.doc.toString(),
|
text: this._editor.state.doc.toString(),
|
||||||
pos: 0,
|
pos: 0,
|
||||||
len: this._editor.state.doc.length,
|
len: this._editor.state.doc.length,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
this._appendInsertPos = undefined;
|
||||||
inputs = ranges.map(r => ({
|
inputs = ranges.map(r => ({
|
||||||
text: this._editor.state.doc.slice(r.from, r.to).toString(),
|
text: this._editor.state.doc.slice(r.from, r.to).toString(),
|
||||||
pos: r.from,
|
pos: r.from,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
html {}
|
html {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -3,6 +3,7 @@ module dequoter
|
||||||
go 1.25
|
go 1.25
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
|
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
|
||||||
github.com/wailsapp/wails/v2 v2.10.2
|
github.com/wailsapp/wails/v2 v2.10.2
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
|
@ -49,7 +50,6 @@ require (
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||||
github.com/labstack/echo/v4 v4.13.3 // indirect
|
github.com/labstack/echo/v4 v4.13.3 // indirect
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 404 KiB |
3
main.go
3
main.go
|
|
@ -60,7 +60,8 @@ func main() {
|
||||||
Bind: []interface{}{
|
Bind: []interface{}{
|
||||||
app,
|
app,
|
||||||
},
|
},
|
||||||
Menu: appMenu,
|
EnableDefaultContextMenu: true,
|
||||||
|
Menu: appMenu,
|
||||||
Mac: &mac.Options{
|
Mac: &mac.Options{
|
||||||
About: &mac.AboutInfo{
|
About: &mac.AboutInfo{
|
||||||
Title: "Dequoter",
|
Title: "Dequoter",
|
||||||
|
|
|
||||||
13
package-lock.json
generated
13
package-lock.json
generated
|
|
@ -5,6 +5,7 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"@hotwired/stimulus": "^3.2.2",
|
"@hotwired/stimulus": "^3.2.2",
|
||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"mousetrap": "^1.6.5"
|
"mousetrap": "^1.6.5"
|
||||||
|
|
@ -79,6 +80,18 @@
|
||||||
"@marijn/find-cluster-break": "^1.0.0"
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/theme-one-dark": {
|
||||||
|
"version": "6.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
|
||||||
|
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@codemirror/view": {
|
"node_modules/@codemirror/view": {
|
||||||
"version": "6.38.2",
|
"version": "6.38.2",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.2.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"@hotwired/stimulus": "^3.2.2",
|
"@hotwired/stimulus": "^3.2.2",
|
||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"mousetrap": "^1.6.5"
|
"mousetrap": "^1.6.5"
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,13 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
@ -59,14 +61,25 @@ var TextFilters = map[string]TextProcessor{
|
||||||
return TextFilterResponse{Output: strings.ToLower(input)}, nil
|
return TextFilterResponse{Output: strings.ToLower(input)}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"quote": {
|
||||||
|
Label: "String: Quote",
|
||||||
|
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||||
|
return TextFilterResponse{Output: strconv.Quote(input)}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
"unquote": {
|
"unquote": {
|
||||||
Label: "String: Unquote",
|
Label: "String: Unquote",
|
||||||
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||||
out, err := strconv.Unquote(input)
|
endNL := ""
|
||||||
|
if strings.HasSuffix(input, "\n") {
|
||||||
|
endNL = "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := strconv.Unquote(strings.TrimSpace(input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TextFilterResponse{}, err
|
return TextFilterResponse{}, err
|
||||||
}
|
}
|
||||||
return TextFilterResponse{Output: out}, nil
|
return TextFilterResponse{Output: out + endNL}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"join-lines-with-commas": {
|
"join-lines-with-commas": {
|
||||||
|
|
@ -175,6 +188,20 @@ var TextFilters = map[string]TextProcessor{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"uuid": {
|
||||||
|
Label: "Generate: UUID v4",
|
||||||
|
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||||
|
u, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
return TextFilterResponse{}, fmt.Errorf("failed to generate UUID: %w", err)
|
||||||
|
}
|
||||||
|
return TextFilterResponse{
|
||||||
|
Output: u.String(),
|
||||||
|
Append: true,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
"lines": {
|
"lines": {
|
||||||
Label: "Lines: Count",
|
Label: "Lines: Count",
|
||||||
Analyze: func(ctx context.Context, input string) (result string, err error) {
|
Analyze: func(ctx context.Context, input string) (result string, err error) {
|
||||||
|
|
@ -264,19 +291,38 @@ var TextFilters = map[string]TextProcessor{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"ucl-evaluate": {
|
"sort-lines-asc": {
|
||||||
Label: "UCL: Evaluate",
|
Label: "Lines: Sort A-Z",
|
||||||
Description: "Evaluates the input as a UCL expression and displays the result in the status bar.",
|
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||||
Analyze: func(ctx context.Context, input string) (result string, err error) {
|
lines := strings.Split(input, "\n")
|
||||||
res, err := uclInstFromContext(ctx).EvalString(ctx, input)
|
sort.Strings(lines)
|
||||||
if err != nil {
|
return TextFilterResponse{Output: strings.Join(lines, "\n")}, nil
|
||||||
if errors.Is(err, ucl.ErrNotConvertable) {
|
},
|
||||||
return "Evaluated successfully. No result.", err
|
},
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("Result: %v", res), nil
|
"sort-lines-desc": {
|
||||||
|
Label: "Lines: Sort Z-A",
|
||||||
|
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||||
|
lines := strings.Split(input, "\n")
|
||||||
|
sort.Sort(sort.Reverse(sort.StringSlice(lines)))
|
||||||
|
return TextFilterResponse{Output: strings.Join(lines, "\n")}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"unique-lines": {
|
||||||
|
Label: "Lines: Unique",
|
||||||
|
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||||
|
lines := strings.Split(input, "\n")
|
||||||
|
seen := make(map[string]struct{}, len(lines))
|
||||||
|
dst := make([]string, 0, len(lines))
|
||||||
|
for _, line := range lines {
|
||||||
|
if _, ok := seen[line]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[line] = struct{}{}
|
||||||
|
dst = append(dst, line)
|
||||||
|
}
|
||||||
|
return TextFilterResponse{Output: strings.Join(dst, "\n")}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -320,22 +366,6 @@ var TextFilters = map[string]TextProcessor{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"ucl-replace": {
|
|
||||||
Label: "UCL: Replace",
|
|
||||||
Description: "Evaluates the input as a UCL expression and replaces it with the result.",
|
|
||||||
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
|
||||||
res, err := uclInstFromContext(ctx).EvalString(ctx, input)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, ucl.ErrNotConvertable) {
|
|
||||||
return TextFilterResponse{}, err
|
|
||||||
}
|
|
||||||
return TextFilterResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return TextFilterResponse{Output: fmt.Sprint(res)}, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type strPseudoVar struct {
|
type strPseudoVar struct {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue