diff --git a/.DS_Store b/.DS_Store index 5cc86e8..eb0ea6a 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/packages/plugs/core/core.plug.yaml b/packages/plugs/core/core.plug.yaml index f75bc88..3c2c404 100644 --- a/packages/plugs/core/core.plug.yaml +++ b/packages/plugs/core/core.plug.yaml @@ -42,14 +42,14 @@ functions: path: ./page.ts:linkQueryProvider events: - query:link - indexItems: - path: "./item.ts:indexItems" - events: - - page:index - itemQueryProvider: - path: ./item.ts:queryProvider - events: - - query:item + # indexItems: + # path: "./item.ts:indexItems" + # events: + # - page:index + # itemQueryProvider: + # path: ./item.ts:queryProvider + # events: + # - query:item deletePage: path: "./page.ts:deletePage" command: @@ -67,6 +67,9 @@ functions: name: "Page: Rename" mac: Cmd-Alt-r key: Ctrl-Alt-r + button: + label: "📝" + tooltip: "Rename page" pageComplete: path: "./page.ts:pageComplete" events: @@ -99,7 +102,20 @@ functions: path: ./page.ts:parsePageCommand command: name: "Debug: Parse Document" - + insertPageMeta: + path: "./page.ts:insertPageMeta" + slashCommand: + name: meta + quickNoteCommand: + path: ./template.ts:quickNoteCommand + command: + name: "Template: Quick Note" + key: "Alt-Shift-n" + quickTaskCommand: + path: ./template.ts:quickTaskCommand + command: + name: "Template: Quick Task" + key: "Alt-Shift-t" instantiateTemplateCommand: path: ./template.ts:instantiateTemplateCommand command: diff --git a/packages/plugs/core/page.ts b/packages/plugs/core/page.ts index f155809..b852f76 100644 --- a/packages/plugs/core/page.ts +++ b/packages/plugs/core/page.ts @@ -9,8 +9,11 @@ import { import { flashNotification, getCurrentPage, + getCursor, getText, + insertAtCursor, matchBefore, + moveCursor, navigate, prompt, } from "@silverbulletmd/plugos-silverbullet-syscall/editor"; @@ -247,3 +250,9 @@ export async function parsePageCommand() { export async function parsePage(text: string) { console.log("AST", JSON.stringify(await parseMarkdown(text), null, 2)); } + +export async function insertPageMeta() { + let cursorPos = await getCursor(); + await insertAtCursor("```meta\n\n```"); + await moveCursor(cursorPos + 8); +} diff --git a/packages/plugs/core/template.ts b/packages/plugs/core/template.ts index b79769b..5a10cf6 100644 --- a/packages/plugs/core/template.ts +++ b/packages/plugs/core/template.ts @@ -5,6 +5,7 @@ import { } from "@silverbulletmd/plugos-silverbullet-syscall/space"; import { filterBox, + moveCursor, navigate, prompt, } from "@silverbulletmd/plugos-silverbullet-syscall/editor"; @@ -35,7 +36,7 @@ export async function instantiateTemplateCommand() { let { text } = await readPage(selectedTemplate.name); let parseTree = await parseMarkdown(text); - let additionalPageMeta = extractMeta(parseTree, true); + let additionalPageMeta = extractMeta(parseTree, ["name"]); console.log("Page meta", additionalPageMeta); let pageName = await prompt("Name of new page", additionalPageMeta.name); @@ -47,6 +48,7 @@ export async function instantiateTemplateCommand() { await navigate(pageName); } +// TODO: This should probably be replaced with handlebards somehow? export function replaceTemplateVars(s: string, pageName: string): string { return s.replaceAll(/\{\{([^\}]+)\}\}/g, (match, v) => { switch (v) { @@ -64,3 +66,22 @@ export function replaceTemplateVars(s: string, pageName: string): string { return match; }); } + +export async function quickNoteCommand() { + let isoDate = new Date().toISOString(); + let [date, time] = isoDate.split("T"); + time = time.split(".")[0]; + let pageName = `📥 ${date} ${time}`; + await writePage(pageName, ""); + await navigate(pageName); +} + +export async function quickTaskCommand() { + let isoDate = new Date().toISOString(); + let [date, time] = isoDate.split("T"); + time = time.split(".")[0]; + let pageName = `✅ ${date} ${time}`; + await writePage(pageName, "* [ ] "); + await navigate(pageName); + await moveCursor(6); +} diff --git a/packages/plugs/github/github.plug.yaml b/packages/plugs/github/github.plug.yaml index b8b20e9..e561abf 100644 --- a/packages/plugs/github/github.plug.yaml +++ b/packages/plugs/github/github.plug.yaml @@ -5,6 +5,6 @@ functions: events: - query:gh-events queryIssues: - path: ./github.ts:queryIssues + path: ./github.ts:queryPulls events: - - query:gh-issues + - query:gh-pulls diff --git a/packages/plugs/github/github.ts b/packages/plugs/github/github.ts index a8a29ac..e147d43 100644 --- a/packages/plugs/github/github.ts +++ b/packages/plugs/github/github.ts @@ -61,9 +61,13 @@ class GithubApi { ); } - async listIssues(filter: string): Promise { + async listPulls( + repo: string, + state: string = "all", + sort: string = "updated" + ): Promise { return this.apiCall( - `https://api.github.com/issues?q=${encodeURIComponent(filter)}` + `https://api.github.com/repos/${repo}/pulls?state=${state}&sort=${sort}&direction=desc&per_page=100` ); } @@ -126,27 +130,40 @@ export async function queryEvents({ return applyQuery(query, allEvents.map(mapEvent)); } -export async function queryIssues({ +function mapPull(pull: any): any { + // console.log("Pull", Object.keys(pull)); + return { + ...pull, + username: pull.user.login, + // repo: pull.repo.name, + createdAt: pull.created_at.split("T")[0], + updatedAt: pull.updated_at.split("T")[0], + }; +} + +export async function queryPulls({ query, }: QueryProviderEvent): Promise { let api = await GithubApi.fromConfig(); - let filter = query.filter.find((f) => f.prop === "filter"); - if (!filter) { - throw Error("No 'filter' specified, this is mandatory"); + let repo = query.filter.find((f) => f.prop === "repo"); + if (!repo) { + throw Error("No 'repo' specified, this is mandatory"); } - let queries: string[] = []; - if (filter.op === "=") { - queries = [filter.value]; - } else if (filter.op === "in") { - queries = filter.value; + query.filter.splice(query.filter.indexOf(repo), 1); + let repos: string[] = []; + if (repo.op === "=") { + repos = [repo.value]; + } else if (repo.op === "in") { + repos = repo.value; } else { - throw new Error(`Unsupported operator ${filter.op}`); + throw new Error(`Unsupported operator ${repo.op}`); } - let allIssues: any[] = []; - for (let issuesList of await Promise.all( - queries.map((query) => api.listIssues(query)) + let allPulls: any[] = []; + for (let pullList of await Promise.all( + repos.map((repo) => api.listPulls(repo, "all", "updated")) )) { - allIssues.push(...issuesList); + allPulls.push(...pullList); } - return allIssues; + allPulls = applyQuery(query, allPulls.map(mapPull)); + return allPulls; } diff --git a/packages/plugs/query/data.ts b/packages/plugs/query/data.ts index 687ad94..ce0be8f 100644 --- a/packages/plugs/query/data.ts +++ b/packages/plugs/query/data.ts @@ -12,10 +12,14 @@ import { ParseTree, replaceNodesMatching, } from "@silverbulletmd/common/tree"; -import { parse as parseYaml, parseAllDocuments } from "yaml"; +import { + parse as parseYaml, + stringify as stringifyYaml, + parseAllDocuments, +} from "yaml"; import type { QueryProviderEvent } from "./engine"; import { applyQuery } from "./engine"; -import { jsonToMDTable, removeQueries } from "./util"; +import { removeQueries } from "./util"; export async function indexData({ name, tree }: IndexTreeEvent) { let dataObjects: { key: string; value: Object }[] = []; @@ -58,8 +62,11 @@ export async function indexData({ name, tree }: IndexTreeEvent) { await batchSet(name, dataObjects); } -export function extractMeta(parseTree: ParseTree, remove = false): any { - let data = {}; +export function extractMeta( + parseTree: ParseTree, + removeKeys: string[] = [] +): any { + let data: any = {}; replaceNodesMatching(parseTree, (t) => { if (t.type !== "FencedCode") { return; @@ -78,7 +85,14 @@ export function extractMeta(parseTree: ParseTree, remove = false): any { } let codeText = codeTextNode.children![0].text!; data = parseYaml(codeText); - return remove ? null : undefined; + if (removeKeys.length > 0) { + let newData = { ...data }; + for (let key of removeKeys) { + delete newData[key]; + } + codeTextNode.children![0].text = stringifyYaml(newData).trim(); + } + return undefined; }); return data; diff --git a/packages/web/components/filter.tsx b/packages/web/components/filter.tsx index 051f00e..ac28152 100644 --- a/packages/web/components/filter.tsx +++ b/packages/web/components/filter.tsx @@ -174,6 +174,7 @@ export function FilterList({ break; } }} + onClick={(e) => e.stopPropagation()} />
void; lhs?: React.ReactNode; rhs?: React.ReactNode; @@ -27,24 +29,11 @@ export function TopBar({ {lhs}
- {/**/} - {/* */} - {/**/} - {prettyName(pageName)} - {/* {*/} - {/* // @ts-ignore*/} - {/* window.syncer();*/} - {/* e.stopPropagation();*/} - {/* }}*/} - {/*>*/} - {/* Sync*/} - {/**/} {notifications.length > 0 && (
{notifications.map((notification) => ( @@ -52,6 +41,20 @@ export function TopBar({ ))}
)} +
+ {actionButtons.map((actionButton, idx) => ( + + ))} +
{rhs} diff --git a/packages/web/editor.tsx b/packages/web/editor.tsx index 4558332..735a41a 100644 --- a/packages/web/editor.tsx +++ b/packages/web/editor.tsx @@ -99,10 +99,11 @@ export class Editor { // Command hook this.commandHook = new CommandHook(); this.commandHook.on({ - commandsUpdated: (commandMap) => { + commandsUpdated: (commandMap, actionButtons) => { this.viewDispatch({ type: "update-commands", commands: commandMap, + actionButtons: actionButtons, }); }, }); @@ -603,6 +604,16 @@ export class Editor { pageName={viewState.currentPage} notifications={viewState.notifications} unsavedChanges={viewState.unsavedChanges} + actionButtons={[ + { + label: "⚡️", + orderId: 0, + run: () => { + this.viewDispatch({ type: "show-palette" }); + }, + }, + ...viewState.actionButtons, + ]} onClick={() => { dispatch({ type: "start-navigate" }); }} diff --git a/packages/web/fonts/LICENSE.md b/packages/web/fonts/LICENSE.md new file mode 100644 index 0000000..5cd41aa --- /dev/null +++ b/packages/web/fonts/LICENSE.md @@ -0,0 +1,100 @@ +# iA Writer Typeface + +Copyright © 2018 Information Architects Inc. with Reserved Font Name "iA Writer" + +# Based on IBM Plex Typeface + +Copyright © 2017 IBM Corp. with Reserved Font Name "Plex" + +# License + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/packages/web/fonts/iAWriterMonoS-Bold.woff2 b/packages/web/fonts/iAWriterMonoS-Bold.woff2 new file mode 100644 index 0000000..1271a12 Binary files /dev/null and b/packages/web/fonts/iAWriterMonoS-Bold.woff2 differ diff --git a/packages/web/fonts/iAWriterMonoS-BoldItalic.woff2 b/packages/web/fonts/iAWriterMonoS-BoldItalic.woff2 new file mode 100644 index 0000000..34ff801 Binary files /dev/null and b/packages/web/fonts/iAWriterMonoS-BoldItalic.woff2 differ diff --git a/packages/web/fonts/iAWriterMonoS-Italic.woff2 b/packages/web/fonts/iAWriterMonoS-Italic.woff2 new file mode 100644 index 0000000..c76d4ce Binary files /dev/null and b/packages/web/fonts/iAWriterMonoS-Italic.woff2 differ diff --git a/packages/web/fonts/iAWriterMonoS-Regular.woff2 b/packages/web/fonts/iAWriterMonoS-Regular.woff2 new file mode 100644 index 0000000..5c36051 Binary files /dev/null and b/packages/web/fonts/iAWriterMonoS-Regular.woff2 differ diff --git a/packages/web/hooks/command.ts b/packages/web/hooks/command.ts index 718649c..fbfb5cc 100644 --- a/packages/web/hooks/command.ts +++ b/packages/web/hooks/command.ts @@ -1,6 +1,7 @@ import { Hook, Manifest } from "@plugos/plugos/types"; import { System } from "@plugos/plugos/system"; import { EventEmitter } from "@plugos/plugos/event"; +import { ActionButton } from "../types"; export type CommandDef = { name: string; @@ -10,6 +11,14 @@ export type CommandDef = { // Bind to keyboard shortcut key?: string; mac?: string; + + // Action button + button?: ButtonDef; +}; + +export type ButtonDef = { + label: string; + tooltip?: string; }; export type AppCommand = { @@ -22,7 +31,10 @@ export type CommandHookT = { }; export type CommandHookEvents = { - commandsUpdated(commandMap: Map): void; + commandsUpdated( + commandMap: Map, + appButtons: ActionButton[] + ): void; }; export class CommandHook @@ -30,9 +42,11 @@ export class CommandHook implements Hook { editorCommands = new Map(); + actionButtons: ActionButton[] = []; buildAllCommands(system: System) { this.editorCommands.clear(); + this.actionButtons = []; for (let plug of system.loadedPlugs.values()) { for (const [name, functionDef] of Object.entries( plug.manifest!.functions @@ -47,9 +61,18 @@ export class CommandHook return plug.invoke(name, []); }, }); + if (cmd.button) { + this.actionButtons.push({ + label: cmd.button.label, + tooltip: cmd.button.tooltip, + run: () => { + return plug.invoke(name, []); + }, + }); + } } } - this.emit("commandsUpdated", this.editorCommands); + this.emit("commandsUpdated", this.editorCommands, this.actionButtons); } apply(system: System): void { diff --git a/packages/web/reducer.ts b/packages/web/reducer.ts index 9168870..348ea02 100644 --- a/packages/web/reducer.ts +++ b/packages/web/reducer.ts @@ -67,6 +67,7 @@ export default function reducer( return { ...state, commands: action.commands, + actionButtons: action.actionButtons, }; case "show-notification": return { diff --git a/packages/web/styles/editor.scss b/packages/web/styles/editor.scss index 7fe3557..8f45a18 100644 --- a/packages/web/styles/editor.scss +++ b/packages/web/styles/editor.scss @@ -249,7 +249,7 @@ color: #989797; background-color: rgba(210, 210, 210, 0.2); border-radius: 5px; - font-style: italic; + // font-style: italic; font-size: 75%; line-height: 75%; } diff --git a/packages/web/styles/main.scss b/packages/web/styles/main.scss index cf66395..72d7302 100644 --- a/packages/web/styles/main.scss +++ b/packages/web/styles/main.scss @@ -2,11 +2,38 @@ @use "filter_box.scss"; @import "constants"; +@font-face { + font-family: "iA-Mono"; + src: url("../fonts/iAWriterMonoS-Regular.woff2"); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: "iA-Mono"; + src: url("../fonts/iAWriterMonoS-Bold.woff2"); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: "iA-Mono"; + src: url("../fonts/iAWriterMonoS-Italic.woff2"); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: "iA-Mono"; + src: url("../fonts/iAWriterMonoS-BoldItalic.woff2"); + font-weight: bold; + font-style: italic; +} :root { --ident: 18px; /* --editor-font: "Avenir"; */ - --editor-font: "Menlo"; + --editor-font: "iA-Mono", "Menlo"; --ui-font: "Arial"; --top-bar-bg: rgb(41, 41, 41); --highlight-color: #464cfc; @@ -73,6 +100,20 @@ body { color: #5e5e5e; } } + .actions { + float: right; + + button { + border: 0; + // border-radius: 5px; + // background-color: rgba(77,141,255,0.07); + background-color: transparent; + padding: 3px; + font-family: "IA-Mono", "Menlo"; + font-size: 80%; + } + } + } diff --git a/packages/web/types.ts b/packages/web/types.ts index bfd7568..e2a2fe4 100644 --- a/packages/web/types.ts +++ b/packages/web/types.ts @@ -9,6 +9,13 @@ export type Notification = { date: Date; }; +export type ActionButton = { + label: string; + tooltip?: string; + orderId?: number; + run: () => void; +}; + export type AppViewState = { currentPage?: string; showPageNavigator: boolean; @@ -23,6 +30,7 @@ export type AppViewState = { allPages: Set; commands: Map; notifications: Notification[]; + actionButtons: ActionButton[]; showFilterBox: boolean; filterBoxLabel: string; @@ -45,6 +53,7 @@ export const initialViewState: AppViewState = { allPages: new Set(), commands: new Map(), notifications: [], + actionButtons: [], showFilterBox: false, filterBoxHelpText: "", filterBoxLabel: "", @@ -60,7 +69,11 @@ export type Action = | { type: "page-saved" } | { type: "start-navigate" } | { type: "stop-navigate" } - | { type: "update-commands"; commands: Map } + | { + type: "update-commands"; + commands: Map; + actionButtons: ActionButton[]; + } | { type: "show-palette"; context?: string } | { type: "hide-palette" } | { type: "show-notification"; notification: Notification }