From 58cf21d8e1556360d458f9ff6660981a3d5e2170 Mon Sep 17 00:00:00 2001 From: MickaelK Date: Sat, 30 Dec 2023 02:14:12 +1100 Subject: [PATCH] chore (migration): viewerpage editor and audio player --- public/assets/components/menubar.js | 6 +- public/assets/lib/chromecast.js | 69 ++ public/assets/lib/path.js | 4 + public/assets/pages/filespage/model_files.js | 20 + .../pages/viewerpage/application_audio.js | 71 +- .../pages/viewerpage/application_editor.css | 345 ++++++++- .../pages/viewerpage/application_editor.js | 201 ++++- .../viewerpage/application_editor/clike.js | 3 + .../viewerpage/application_editor/clojure.js | 3 + .../viewerpage/application_editor/cmake.js | 3 + .../application_editor/commonlisp.js | 3 + .../viewerpage/application_editor/css.js | 3 + .../viewerpage/application_editor/diff.js | 3 + .../application_editor/dockerfile.js | 3 + .../viewerpage/application_editor/elm.js | 3 + .../application_editor/emacs-org.js | 728 ++++++++++++++++++ .../viewerpage/application_editor/erlang.js | 3 + .../pages/viewerpage/application_editor/go.js | 3 + .../application_editor/htmlmixed.js | 3 + .../application_editor/javascript.js | 3 + .../viewerpage/application_editor/jsx.js | 3 + .../application_editor/keymap_base.js | 1 + .../application_editor/keymap_vim.js | 1 + .../viewerpage/application_editor/lua.js | 3 + .../viewerpage/application_editor/orgmode.js | 313 ++++++++ .../viewerpage/application_editor/perl.js | 3 + .../viewerpage/application_editor/php.js | 3 + .../application_editor/properties.js | 3 + .../viewerpage/application_editor/python.js | 3 + .../pages/viewerpage/application_editor/r.js | 3 + .../viewerpage/application_editor/ruby.js | 3 + .../viewerpage/application_editor/rust.js | 3 + .../viewerpage/application_editor/sass.js | 3 + .../viewerpage/application_editor/shell.js | 3 + .../viewerpage/application_editor/sparql.js | 3 + .../application_editor/spreadsheet.js | 3 + .../viewerpage/application_editor/sql.js | 3 + .../viewerpage/application_editor/stex.js | 3 + .../viewerpage/application_editor/text.js | 1 + .../viewerpage/application_editor/xml.js | 3 + .../application_editor/yaml-frontmatter.js | 4 + .../viewerpage/application_editor/yaml.js | 3 + .../viewerpage/application_editor_orgmode.js | 8 + .../pages/viewerpage/application_video.js | 6 +- public/assets/pages/viewerpage/common.js | 2 +- public/assets/pages/viewerpage/model_files.js | 9 + 46 files changed, 1821 insertions(+), 55 deletions(-) create mode 100644 public/assets/lib/chromecast.js create mode 100644 public/assets/pages/viewerpage/application_editor/clike.js create mode 100644 public/assets/pages/viewerpage/application_editor/clojure.js create mode 100644 public/assets/pages/viewerpage/application_editor/cmake.js create mode 100644 public/assets/pages/viewerpage/application_editor/commonlisp.js create mode 100644 public/assets/pages/viewerpage/application_editor/css.js create mode 100644 public/assets/pages/viewerpage/application_editor/diff.js create mode 100644 public/assets/pages/viewerpage/application_editor/dockerfile.js create mode 100644 public/assets/pages/viewerpage/application_editor/elm.js create mode 100644 public/assets/pages/viewerpage/application_editor/emacs-org.js create mode 100644 public/assets/pages/viewerpage/application_editor/erlang.js create mode 100644 public/assets/pages/viewerpage/application_editor/go.js create mode 100644 public/assets/pages/viewerpage/application_editor/htmlmixed.js create mode 100644 public/assets/pages/viewerpage/application_editor/javascript.js create mode 100644 public/assets/pages/viewerpage/application_editor/jsx.js create mode 100644 public/assets/pages/viewerpage/application_editor/keymap_base.js create mode 100644 public/assets/pages/viewerpage/application_editor/keymap_vim.js create mode 100644 public/assets/pages/viewerpage/application_editor/lua.js create mode 100644 public/assets/pages/viewerpage/application_editor/orgmode.js create mode 100644 public/assets/pages/viewerpage/application_editor/perl.js create mode 100644 public/assets/pages/viewerpage/application_editor/php.js create mode 100644 public/assets/pages/viewerpage/application_editor/properties.js create mode 100644 public/assets/pages/viewerpage/application_editor/python.js create mode 100644 public/assets/pages/viewerpage/application_editor/r.js create mode 100644 public/assets/pages/viewerpage/application_editor/ruby.js create mode 100644 public/assets/pages/viewerpage/application_editor/rust.js create mode 100644 public/assets/pages/viewerpage/application_editor/sass.js create mode 100644 public/assets/pages/viewerpage/application_editor/shell.js create mode 100644 public/assets/pages/viewerpage/application_editor/sparql.js create mode 100644 public/assets/pages/viewerpage/application_editor/spreadsheet.js create mode 100644 public/assets/pages/viewerpage/application_editor/sql.js create mode 100644 public/assets/pages/viewerpage/application_editor/stex.js create mode 100644 public/assets/pages/viewerpage/application_editor/text.js create mode 100644 public/assets/pages/viewerpage/application_editor/xml.js create mode 100644 public/assets/pages/viewerpage/application_editor/yaml-frontmatter.js create mode 100644 public/assets/pages/viewerpage/application_editor/yaml.js create mode 100644 public/assets/pages/viewerpage/application_editor_orgmode.js create mode 100644 public/assets/pages/viewerpage/model_files.js diff --git a/public/assets/components/menubar.js b/public/assets/components/menubar.js index 1facd7c0..25574ebe 100644 --- a/public/assets/components/menubar.js +++ b/public/assets/components/menubar.js @@ -18,7 +18,7 @@ export default class ComponentMenubar extends window.HTMLElement { ${basename(decodeURIComponent(location.pathname + location.hash)) || " "}
-
+
@@ -39,7 +39,9 @@ export default class ComponentMenubar extends window.HTMLElement { } render($fragment) { - this.querySelector(".action-item").appendChild($fragment); + const $item = this.querySelector(".action-item"); + $item.replaceChildren($fragment); + animate($item, { time: 250, keyframes: slideYIn(2) }); } } diff --git a/public/assets/lib/chromecast.js b/public/assets/lib/chromecast.js new file mode 100644 index 00000000..b2f3ede0 --- /dev/null +++ b/public/assets/lib/chromecast.js @@ -0,0 +1,69 @@ +class ChromecastManager { + init() { + // TODO: additional rules for setup + let src = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"; + if (document.head.querySelector(`script[src="${src}"]`)) return Promise.resolve(); + + return new Promise((done) => { + const script = document.createElement("script"); + script.src = src; + script.onerror = () => done(); + window["__onGCastApiAvailable"] = function(isAvailable) { + if (isAvailable) cast.framework.CastContext.getInstance().setOptions({ + receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, + autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, + }); + done(); + }; + document.head.appendChild(script) + }); + } + + origin() { + return location.origin; + }; + + isAvailable() { + if (!window.chrome) return false; + else if (!window.chrome.cast) return false; + return window.chrome.cast.isAvailable; + } + + // createLink(apiPath) { + // const target = new URL(this.origin() + apiPath); + // const shareID = new window.URL(location.href).searchParams.get("share"); + // if (shareID) target.searchParams.append("share", shareID); + // return target.toString(); + // } + + createRequest(mediaInfo, authorization) { + if (!authorization) Promise.error(new Error("Invalid account")); + + // TODO: it would be much much nicer to set the authorization in an HTTP header + // but this would require to create a custom web receiver app, setup accounts on + // google, etc,... Until that happens, we're setting the authorization within the + // url. Once we have that app, the authorisation will come from a customData field + // of a chrome.cast.media.LoadRequest + const target = new URL(mediaInfo.contentId); + target.searchParams.append("authorization", Session.authorization); + mediaInfo.contentId = target.toString(); + return new chrome.cast.media.LoadRequest(mediaInfo); + } + + context() { + if (!this.isAvailable()) return + return window.cast.framework.CastContext.getInstance(); + } + session() { + const context = this.context(); + if (!context) return; + return context.getCurrentSession(); + } + media() { + const session = this.session(); + if (!session) return; + return session.getMediaSession(); + } +} + +export default new ChromecastManager(); diff --git a/public/assets/lib/path.js b/public/assets/lib/path.js index e0510c41..ce54f952 100644 --- a/public/assets/lib/path.js +++ b/public/assets/lib/path.js @@ -2,6 +2,10 @@ export function basename(str, sep = "/") { return str.substr(str.lastIndexOf(sep) + 1); } +export function extname(str) { + return str.substr(str.lastIndexOf(".") + 1).toLowerCase(); +} + export function join(baseURL, segment) { return new URL(segment, baseURL).pathname; } diff --git a/public/assets/pages/filespage/model_files.js b/public/assets/pages/filespage/model_files.js index b2a5c4be..8bc20660 100644 --- a/public/assets/pages/filespage/model_files.js +++ b/public/assets/pages/filespage/model_files.js @@ -18,6 +18,26 @@ export function getSelection$() { return selection$.asObservable(); } +// export function ls() { +// return rxjs.from(new Error("missing cache")).pipe( +// rxjs.catchError(() => rxjs.of({ files: null })), +// rxjs.mergeMap(({ files: filesInCache }) => ajax({ +// url: `/api/files/ls?path=${path}`, +// responseType: "json" +// }).pipe( +// rxjs.map(({ responseJSON }) => responseJSON), +// rxjs.filter(({ filesInRemote }) => { +// if (!Array.isArray(filesInCache)) return true; +// if (filesInCache.length != filesInRemote.length) return true; +// for (let i=0; i ajax({ diff --git a/public/assets/pages/viewerpage/application_audio.js b/public/assets/pages/viewerpage/application_audio.js index 51f4d825..ff747c04 100644 --- a/public/assets/pages/viewerpage/application_audio.js +++ b/public/assets/pages/viewerpage/application_audio.js @@ -4,21 +4,28 @@ import { qs } from "../../lib/dom.js"; import { onDestroy } from "../../lib/skeleton/lifecycle.js"; import { loadCSS, loadJS } from "../../helpers/loader.js"; import { settings_get, settings_put } from "../../lib/settings.js"; +import Chromecast from "../../lib/chromecast.js"; import assert from "../../lib/assert.js"; +import { basename, extname } from "../../lib/path.js"; import ctrlError from "../ctrl_error.js"; +import { render as renderMenubar } from "../../components/menubar.js"; +import { menubarDownload, menubarChromecast, buildMenubar } from "./common_menubar.js"; import { ICON } from "./common_icon.js"; import { formatTimecode } from "./common_player.js"; import { transition, getDownloadUrl } from "./common.js"; +import { getSession } from "../../model/session.js"; +import { get as getConfig } from "../../model/config.js"; + import "../../components/menubar.js"; const STATUS_PLAYING = "PLAYING"; const STATUS_PAUSED = "PAUSED"; const STATUS_BUFFERING = "BUFFERING"; -export default function(render) { +export default function(render, { mime }) { const $page = createElement(`
@@ -268,6 +275,9 @@ export default function(render) { case "KeyL": setSeek(Math.min(wavesurfer.getDuration(), currentTime(wavesurfer) + 10), wavesurfer); break; + case "KeyF": + chromecastLoader(); + break; case "KeyJ": setSeek(Math.max(0, currentTime(wavesurfer) - 10), wavesurfer); break; @@ -305,11 +315,70 @@ export default function(render) { }), )), )); + + // feature9: setup chromecast + effect(ready$.pipe( + rxjs.tap(() => renderMenubar(buildMenubar( + menubarChromecast(), + menubarDownload(), + ))), + )); + // effect(rxjs.combineLatest( + // setup$, + // getSession(), + // getConfig(), + // ).pipe( + // rxjs.mergeMap(async ([wavesurfer, user, config]) => { + // if (!Chromecast.isAvailable()) return; + // const filename = basename(decodeURIComponent(location.pathname)); + // // const link = Chromecast.createLink(getDownloadUrl()); + // const media = new chrome.cast.media.MediaInfo( + // getDownloadUrl(), + // mime, + // ); + // media.metadata = new chrome.cast.media.MusicTrackMediaMetadata() + // media.metadata.title = "test"; + // media.metadata.title = filename.substr(0, filename.lastIndexOf(extname(filename))); + // media.metadata.subtitle = config.name; + // media.metadata.albumName = config.name; + // media.metadata.images = [ + // new chrome.cast.Image(origin + "/assets/icons/music.png"), + // ]; + // wavesurfer.setMute(true); + // wavesurfer.pause(); + + // const session = Chromecast.session(); + // if (!session) return + // setVolume(session.getVolume() * 100); + + // const req = await Chromecast.createRequest(media, user.authorization); + // return session.loadMedia(req); + // // .catch((err) => { + // // console.error(err); + // // notify.send(t("Cannot establish a connection"), "error"); + // // setIsChromecast(false); + // // setIsLoading(false); + // // }); + // }), + // )); } export function init() { return Promise.all([ + setup_chromecast(), loadJS(import.meta.url, "../../lib/vendor/wavesurfer.js"), loadCSS(import.meta.url, "./application_audio.css"), ]); } + +function setup_chromecast() { + if (!("chrome" in window)) { + return Promise.resolve(); + } else if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { + return Promise.resolve(); + } + // if (!CONFIG.enable_chromecast) { + // return Promise.resolve(); + // } else + return Chromecast.init(); +} diff --git a/public/assets/pages/viewerpage/application_editor.css b/public/assets/pages/viewerpage/application_editor.css index e7a186c6..005820a8 100644 --- a/public/assets/pages/viewerpage/application_editor.css +++ b/public/assets/pages/viewerpage/application_editor.css @@ -1,45 +1,334 @@ .component_editor { - overflow-y: auto; - flex-direction: column; - flex: 1; + display: flex; + flex-grow: 1; + min-height: 0; width: 100%; } +.component_editor > div { + width: 100%; + position: relative; +} +.component_editor > div #editor { + width: 100%; + position: absolute; + left: 0; + top: 0; + bottom: 0; +} .CodeMirror { width: 100%; height: 100%; + color: #3b4045; background: var(--bg-color); +} + +.CodeMirror-sizer > div { + padding-top: 4px; + padding-bottom: 5px; +} + +.CodeMirror-scroll { + -webkit-overflow-scrolling: touch; +} + +.CodeMirror-foldmarker { + padding: 5px; +} + +/* HIDE LINE NUMBERS ON MOBILE */ +@media screen and (max-width: 400px) { + .CodeMirror-sizer { + margin-left: 0 !important; + } + .CodeMirror-gutters { + display: none; + } + .CodeMirror-gutter-wrapper { + display: none; + } +} + +.CodeMirror-linenumber { + cursor: pointer; +} + +/* SEARCH */ +.CodeMirror-dialog { + position: fixed; + left: 0; + right: 0; + background: #525659; + z-index: 15; + padding: 5px .8em; + overflow: hidden; + color: #e2e2e2; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5); +} + +.CodeMirror-dialog-top { + border-bottom: 1px solid #eee; + bottom: 0; +} + +.CodeMirror-dialog-bottom { + border-top: 1px solid #eee; + bottom: 0; +} + +.CodeMirror-dialog input { + border: none; + outline: none; + background: transparent; + width: 20em; + color: white; +} + +.CodeMirror-dialog button { + font-size: 70%; +} + +/* Font stuff */ +.CodeMirror { font-size: 16px; - font-family: "Source Code Pro", monospace; + font-family: 'Source Code Pro', monospace; } -.CodeMirror-scroll { -webkit-overflow-scrolling: touch; } +.cm-s-default .cm-header { + font-size: 18px; +} -@font-face { - font-family: "Source Code Pro"; - font-style: normal; - font-weight: 400; - src: local("Source Code Pro"), local("SourceCodePro-Regular"), url(/assets/fonts/SourceCodePro-Regular-400-latin-ext.woff2) format("woff2"); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +.cm-s-default .cm-header.cm-level1 { + font-size: 19px; } -@font-face { - font-family: "Source Code Pro"; - font-style: normal; - font-weight: 400; - src: local("Source Code Pro"), local("SourceCodePro-Regular"), url(/assets/fonts/SourceCodePro-Regular-400-latin.woff2) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + +@media only screen and (max-width: 600px) { + .CodeMirror { + font-size: 14px; + } + .cm-s-default .cm-header { + font-size: 15px; + } + .cm-s-default .cm-header.cm-level1 { + font-size: 16px; + } } -@font-face { - font-family: "Source Code Pro"; - font-style: normal; + +/* Make things more confy */ +.CodeMirror .CodeMirror-code { + line-height: 1.3em; +} +.CodeMirror .CodeMirror-code > div { + clear: both; +} + +.CodeMirror[mode="orgmode"] .CodeMirror-code { + line-height: 1.5em; +} + +.CodeMirror .CodeMirror-line { + padding-left: 10%; + padding-right: 0; + max-width: 950px; +} +@media screen and (max-width: 1150px) { + .CodeMirror .CodeMirror-line { + padding-left: 8%; + padding-right: 0; + } +} +@media screen and (max-width: 900px) { + .CodeMirror .CodeMirror-line { + padding-left: 5%; + padding-right: 0; + } +} +@media screen and (max-width: 800px) { + .CodeMirror .CodeMirror-line { + padding-left: 2%; + padding-right: 0; + } +} +.CodeMirror .CodeMirror-linenumber { + color: var(--color); + opacity: 0.6; +} + +.CodeMirror .CodeMirror-gutters { + box-shadow: none; + background-color: inherit; + border-right: none; +} + +/* Widget stuff */ +.CodeMirror-linewidget img { + cursor: pointer; + margin: 10px; + height: 300px; + max-width: 80%; + text-align: center; + box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5); + background: var(--dark); +} + +/* Code Highlight Theme */ +.cm-s-default .cm-header { + color: #3E7AA6; + line-height: 1em; font-weight: 600; - src: local("Source Code Pro Semibold"), local("SourceCodePro-Semibold"), url(/assets/fonts/SourceCodePro-Semibold-600-latin-ext.woff2) format("woff2"); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } -@font-face { - font-family: "Source Code Pro"; - font-style: normal; - font-weight: 600; - src: local("Source Code Pro Semibold"), local("SourceCodePro-Semibold"), url(/assets/fonts/SourceCodePro-Semibold-600-latin.woff2) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + +.cm-header.cm-level1 { + color: #376e95; +} + +.cm-s-default .cm-keyword { + color: var(--emphasis-secondary); +} + +.cm-s-default .cm-header.cm-org-level-star { + color: #6f6f6f; + vertical-align: baseline; + display: inline-block; + padding-left: 5px; + margin-left: -5px; + cursor: pointer; +} + +.cm-s-default .cm-header.cm-org-todo { + color: #FF8355; + font-weight: normal; + cursor: pointer; +} + +.cm-s-default .cm-header.cm-org-done { + color: #3BB27C; + font-weight: normal; + cursor: pointer; +} + +.cm-s-default .cm-header.cm-org-priority { + cursor: pointer; + font-weight: normal; +} + +.cm-s-default .cm-org-toggle { + cursor: pointer; + background: var(--light); + color: var(--super-light); + border-radius: 3px; + font-weight: bold; + padding-bottom: 2px; + vertical-align: text-bottom; + opacity: 0.7; +} + +.cm-s-default .cm-void { + display: inline-block; + max-width: 10px; + overflow: hidden; + white-space: nowrap; +} + +.cm-s-default .cm-header.cm-comment { + font-weight: normal; + font-size: 0.9em !important; + float: right; + display: inline-block; + color: var(--emphasis); + line-height: 23px; +} + +pre.CodeMirror-line { + clear: right; +} + +.cm-s-default .cm-link { + color: var(--emphasis); +} + +.cm-s-default .cm-strong { + color: var(--emphasis); +} + +.cm-s-default .cm-org-url, .cm-s-default .cm-org-image { + color: var(--emphasis) !important; + border-bottom: 1px dashed var(--light); + cursor: pointer; +} + +.cm-s-default .cm-variable-3 { + color: #085; +} + +.cm-s-default .cm-comment { + color: var(--light); +} + +.cm-s-default .cm-string, .cm-s-default .cm-string-2 { + color: #c41a16; +} + +.cm-s-default .cm-def { + color: #445588; +} + +.cm-s-default .cm-quote { + color: var(--dark); +} + +.CodeMirror-gutters { + box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.1); +} + +.CodeMirror-foldmarker { + padding-left: 5px; + color: var(--color); + text-shadow: 1px 1px 10px var(--color); +} + +span.CodeMirror-matchingbracket, +span.CodeMirror-matchingtag { + background: rgba(0, 0, 0, 0.1); + color: inherit !important; +} + +/* BUGFIX */ +.CodeMirror-cursor { + min-width: 1px !important; +} + +/* DAR MODE THEME */ +.dark-mode .CodeMirror { + background: #242424; +} +.dark-mode .CodeMirror, .dark-mode .CodeMirror .CodeMirror-linenumber, .dark-mode .CodeMirror .cm-variable-2, .dark-mode .CodeMirror .cm-variable-3, .dark-mode .CodeMirror .cm-number, .dark-mode .CodeMirror .cm-quote { + color: #f6f3e8; +} +.dark-mode .CodeMirror .cm-string, .dark-mode .CodeMirror .cm-string-2 { + color: #95e454; +} +.dark-mode .CodeMirror .cm-atom { + color: #e5786d; +} +.dark-mode .CodeMirror .cm-keyword, .dark-mode .CodeMirror .cm-meta, .dark-mode .CodeMirror .cm-header, .dark-mode .CodeMirror .cm-property { + color: #8ac6f2; +} +.dark-mode .CodeMirror .cm-def, .dark-mode .CodeMirror .cm-tag, .dark-mode .CodeMirror .cm-attribute, .dark-mode .CodeMirror .cm-builtin, .dark-mode .CodeMirror .cm-qualifier { + color: #cae682; +} +.dark-mode .CodeMirror .cm-comment { + color: #99968b; +} +.dark-mode .CodeMirror .CodeMirror-cursor { + background-color: #99968b; +} +.dark-mode .CodeMirror .CodeMirror-selected { + background-color: rgba(0, 0, 0, 0.3); +} +.dark-mode .CodeMirror .cm-error { + color: var(--error); +} +.dark-mode .CodeMirror .cm-org-url, .dark-mode .CodeMirror .cm-link, .dark-mode .CodeMirror .cm-org-image, .dark-mode .CodeMirror .cm-url { + color: #ccaa8f !important; } diff --git a/public/assets/pages/viewerpage/application_editor.js b/public/assets/pages/viewerpage/application_editor.js index 22057ce9..fd248e0a 100644 --- a/public/assets/pages/viewerpage/application_editor.js +++ b/public/assets/pages/viewerpage/application_editor.js @@ -1,15 +1,22 @@ -import { createElement } from "../../lib/skeleton/index.js"; +import { createElement, onDestroy } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; +import { animate, slideXIn, opacityOut } from "../../lib/animate.js"; import { qs } from "../../lib/dom.js"; import { createLoader } from "../../components/loader.js"; import { loadCSS, loadJS } from "../../helpers/loader.js"; import ajax from "../../lib/ajax.js"; +import { extname } from "../../lib/path.js"; +import { get as getConfig } from "../../model/config.js"; import ctrlError from "../ctrl_error.js"; import ctrlDownloader, { init as initDownloader } from "./application_downloader.js"; -import { transition, getDownloadUrl } from "./common.js"; +import { getFile$, saveFile$, transition, getFilename, getCurrentPath } from "./common.js"; +import { $ICON } from "./common_fab.js"; +import { fileOptions } from "./model_files.js"; import "../../components/menubar.js"; +import "../../components/fab.js"; +import "../../components/icon.js"; const TIME_BEFORE_ABORT_EDIT = 5000; @@ -18,20 +25,21 @@ export default async function(render) {
+
`); render($page); const $editor = qs($page, ".component_editor"); const $menubar = qs($page, "component-menubar"); - const content$ = ajax(getDownloadUrl()).pipe( - rxjs.map(({ response }) => response), - rxjs.shareReplay(), - ); + const $fab = qs($page, `[is="component-fab"]`); + const getConfig$ = getConfig().pipe(rxjs.shareReplay(1)); + const content$ = new rxjs.ReplaySubject(1); + // feature1: setup the dom const removeLoader = createLoader($page); - effect(rxjs.race( - ajax(getDownloadUrl()).pipe(rxjs.map(({ response }) => response)), + const setup$ = rxjs.race( + getFile$(), ajax("/about").pipe(rxjs.delay(TIME_BEFORE_ABORT_EDIT), rxjs.map(() => null)), ).pipe( rxjs.mergeMap((content) => { @@ -46,34 +54,104 @@ export default async function(render) { } return rxjs.of(content); }), + rxjs.mergeMap((content) => getConfig$.pipe( + rxjs.mergeMap((config) => rxjs.from(loadKeybinding(config.editor)).pipe(rxjs.mapTo(config))), + rxjs.map((config) => [content, config]), + rxjs.mergeMap((arr) => rxjs.from(loadMode(extname(getFilename()))).pipe( + rxjs.map((mode) => arr.concat([mode])), + )), + rxjs.mergeMap((arr) => fileOptions(getCurrentPath()).pipe( + rxjs.map((acl) => arr.concat([acl])), + )), + )), removeLoader, - rxjs.tap((content) => { + rxjs.map(([content, config, mode, acl]) => { + content$.next(content); $editor.classList.remove("hidden"); - window.CodeMirror($editor, { + const editor = window.CodeMirror($editor, { value: content, lineNumbers: true, - // mode: mode, - // keyMap: ["emacs", "vim"].indexOf(CONFIG["editor"]) === -1 ? - // "sublime" : CONFIG["editor"], + mode, + keyMap: ["emacs", "vim"].indexOf(config["editor"]) === -1 ? "sublime" : config["editor"], lineWrapping: true, - // readOnly: !this.props.readonly, - foldOptions: { - widget: "...", - }, + readOnly: !/PUT/.test(acl), + foldOptions: { widget: "..." }, matchBrackets: {}, autoCloseBrackets: true, matchTags: { bothTags: true }, autoCloseTags: true, }); transition($editor); + editor.getWrapperElement().setAttribute("mode", mode); + if (!("ontouchstart" in window)) editor.focus(); + if (config["editor"] === "emacs") editor.addKeyMap({ + "Ctrl-X Ctrl-C": (cm) => window.history.back(), + }); + onDestroy(() => editor.clearHistory()); $menubar.classList.remove("hidden"); + editor.execCommand("save"); + return editor; + }), + rxjs.tap((editor) => requestAnimationFrame(() => editor.refresh())), + rxjs.share(), + rxjs.catchError(ctrlError()), + ); + effect(setup$); + + // feature2: handle resize + effect(setup$.pipe( + rxjs.mergeMap((editor) => rxjs.fromEvent(window, "resize").pipe( + rxjs.tap(() => editor.refresh()), + )), + )); + + // feature3: handle UI for edit + effect(setup$.pipe( + rxjs.switchMap((editor) => new rxjs.Observable((observer) => editor.on("change", (cm) => observer.next(cm)))), + rxjs.mergeMap((editor) => content$.pipe(rxjs.map((oldContent) => [editor, editor.getValue(), oldContent]))), + rxjs.tap(async ([editor, newContent = "", oldContent = ""]) => { + if ($fab.disabled) return; + else if (newContent === oldContent) { + await animate($fab, { time: 100, keyframes: opacityOut() }); + $fab.classList.add("hidden"); + return + } + const shouldAnimate = $fab.classList.contains("hidden"); + $fab.classList.remove("hidden"); + $fab.render($ICON.SAVING); + $fab.onclick = () => CodeMirror.commands.save(editor); + // TODO: breadcrumb saving hint * + if (shouldAnimate) await animate($fab, { time: 100, keyframes: slideXIn(40) }); + }), + )); + + // feature4: save + effect(setup$.pipe( + rxjs.mergeMap((editor) => new rxjs.Observable((observer) => { + CodeMirror.commands.save = (cm) => observer.next(cm); + })), + rxjs.mergeMap((cm) => { + $fab.render($ICON.LOADING) + $fab.disabled = true; + return rxjs.of(cm.getValue()).pipe( + saveFile$(), + rxjs.tap((content) => { + $fab.removeAttribute("disabled"); + content$.next(content); + }), + ); }), rxjs.catchError(ctrlError()), - )) + )); } function has_binary(str) { - return /\ufffd/.test(str); + let countUnrepresentableChar = 0; + for (let i=0; i 2) return true; + else if (str[i] === "\ufffd") countUnrepresentableChar += 1; + } + return false; } export function init() { @@ -81,5 +159,88 @@ export function init() { loadCSS(import.meta.url, "../../lib/vendor/codemirror/lib/codemirror.css"), loadJS(import.meta.url, "../../lib/vendor/codemirror/lib/codemirror.js"), loadCSS(import.meta.url, "./application_editor.css"), - ]); + ]).then(() => Promise.all([ + loadJS(import.meta.url, "../../lib/vendor/codemirror/keymap/emacs.js"), + // search + loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/search/searchcursor.js"), + loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/search/search.js"), + loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/comment/comment.js"), + loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/dialog/dialog.js"), + // folding + loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/fold/foldcode.js"), + loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/fold/foldgutter.js"), + loadCSS(import.meta.url, "../../lib/vendor/codemirror/addon/fold/foldgutter.css"), + // editing feature + loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/edit/matchbrackets.js"), + loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/edit/closebrackets.js"), + loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/edit/closetag.js"), + ])); +} + +function loadMode(ext) { + let mode = "text"; + let before = Promise.resolve(); + + if (ext === "org" || ext === "org_archive") { + mode = "orgmode"; + before = loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/fold/xml-fold.js").then(() => loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/edit/matchtags.js")); + } else if (ext === "sh") mode = "shell"; + else if (ext === "py") mode = "python"; + else if (ext === "html" || ext === "htm") { + mode = "htmlmixed"; + before = Promise.all([ + loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/xml/xml.js"), + loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/javascript/javascript.js"), + loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/css/css.js"), + ]); + } else if (ext === "css") mode = "css"; + else if (ext === "less" || ext === "scss" || ext === "sass") mode = "sass"; + else if (ext === "js" || ext === "json") mode = "javascript"; + else if (ext === "jsx") mode = "jsx"; + else if (ext === "php" || ext === "php5" || ext === "php4") mode = "php"; + else if (ext === "elm") mode = "elm"; + else if (ext === "erl") mode = "erlang"; + else if (ext === "go") mode = "go"; + else if (ext === "markdown" || ext === "md") { + mode = "yaml-frontmatter"; + before = Promise.all([ + loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/markdown/markdown.js"), + loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/gfm/gfm.js"), + loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/yaml/yaml.js"), + loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/mode/overlay.js"), + ]); + } else if (ext === "pl" || ext === "pm") mode = "perl"; + else if (ext === "clj") mode = "clojure"; + else if (ext === "el" || ext === "lisp" || ext === "cl" || + ext === "emacs") mode = "commonlisp"; + else if (ext === "dockerfile") { + mode = "dockerfile"; + before = loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/mode/simple.js"); + } else if (ext === "R") mode = "r"; + else if (ext === "makefile") mode = "cmake"; + else if (ext === "rb") mode = "ruby"; + else if (ext === "sql") mode = "sql"; + else if (ext === "xml" || ext === "rss" || ext === "svg" || + ext === "atom") mode = "xml"; + else if (ext === "yml" || ext === "yaml") mode = "yaml"; + else if (ext === "lua") mode = "lua"; + else if (ext === "csv") mode = "spreadsheet"; + else if (ext === "rs" || ext === "rlib") mode = "rust"; + else if (ext === "latex" || ext === "tex") mode = "stex"; + else if (ext === "diff" || ext === "patch") mode = "diff"; + else if (ext === "sparql") mode = "sparql"; + else if (ext === "properties") mode = "properties"; + else if (ext === "c" || ext === "cpp" || ext === "java" || + ext === "h") mode = "clike"; + + return before.then(() => loadJS(import.meta.url, `./application_editor/${mode}.js`, { type: "module" })) + .catch(() => loadJS(import.meta.url, "./application_editor/text.js", { type: "module" })) + .then((module) => Promise.resolve(mode)); +} + +function loadKeybinding(editor) { + if (editor === "emacs" || !editor) { + return Promise.resolve(); + } + return loadJS(import.meta.url, `./application_editor/keymap_${editor}.js`, { type: "module" }); } diff --git a/public/assets/pages/viewerpage/application_editor/clike.js b/public/assets/pages/viewerpage/application_editor/clike.js new file mode 100644 index 00000000..a9254619 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/clike.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/clike/clike.js"; +CodeMirror.__mode = "clike"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/clojure.js b/public/assets/pages/viewerpage/application_editor/clojure.js new file mode 100644 index 00000000..26ddf71a --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/clojure.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/clojure/clojure.js"; +CodeMirror.__mode = "clojure"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/cmake.js b/public/assets/pages/viewerpage/application_editor/cmake.js new file mode 100644 index 00000000..1be7b0b1 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/cmake.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/cmake/cmake.js"; +CodeMirror.__mode = "cmake"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/commonlisp.js b/public/assets/pages/viewerpage/application_editor/commonlisp.js new file mode 100644 index 00000000..7bfcdb7d --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/commonlisp.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/commonlisp/commonlisp.js"; +CodeMirror.__mode = "commonlisp"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/css.js b/public/assets/pages/viewerpage/application_editor/css.js new file mode 100644 index 00000000..0bd016cc --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/css.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/css/css.js"; +CodeMirror.__mode = "css"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/diff.js b/public/assets/pages/viewerpage/application_editor/diff.js new file mode 100644 index 00000000..d2f797c0 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/diff.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/diff/diff.js"; +CodeMirror.__mode = "diff"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/dockerfile.js b/public/assets/pages/viewerpage/application_editor/dockerfile.js new file mode 100644 index 00000000..491f9466 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/dockerfile.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/dockerfile/dockerfile.js"; +CodeMirror.__mode = "dockerfile"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/elm.js b/public/assets/pages/viewerpage/application_editor/elm.js new file mode 100644 index 00000000..6e16b925 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/elm.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/elm/elm.js"; +CodeMirror.__mode = "elm"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/emacs-org.js b/public/assets/pages/viewerpage/application_editor/emacs-org.js new file mode 100644 index 00000000..2b9cc54d --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/emacs-org.js @@ -0,0 +1,728 @@ +export const org_cycle = (cm) => { + const pos = cm.getCursor(); + isFold(cm, pos) ? unfold(cm, pos) : fold(cm, pos); +}; + + +const state = { + stab: "CONTENT", +}; +export const org_set_fold = (cm) => { + const cursor = cm.getCursor(); + set_folding_mode(cm, state.stab); + cm.setCursor(cursor); + return state.stab; +}; +/* + * DONE: Global visibility cycling + * TODO: or move to previous table field. + */ +export const org_shifttab = (cm) => { + if (state.stab === "SHOW_ALL") { + state.stab = "OVERVIEW"; + } else if (state.stab === "OVERVIEW") { + state.stab = "CONTENT"; + } else if (state.stab === "CONTENT") { + state.stab = "SHOW_ALL"; + } + set_folding_mode(cm, state.stab); + return state.stab; +}; + + +function set_folding_mode(cm, mode) { + if (mode === "OVERVIEW") { + folding_mode_overview(cm); + } else if (mode === "SHOW_ALL") { + folding_mode_all(cm); + } else if (mode === "CONTENT") { + folding_mode_content(cm); + } + cm.refresh(); + + function folding_mode_overview(cm) { + cm.operation(function() { + for (let i = cm.firstLine(), e = cm.lastLine(); i <= e; i++) { + fold(cm, CodeMirror.Pos(i, 0)); + } + }); + } + function folding_mode_content(cm) { + cm.operation(function() { + let previous_header = null; + for (let i = cm.firstLine(), e = cm.lastLine(); i <= e; i++) { + fold(cm, CodeMirror.Pos(i, 0)); + if (/header/.test(cm.getTokenTypeAt(CodeMirror.Pos(i, 0))) === true) { + const level = cm.getLine(i).replace(/^(\*+).*/, "$1").length; + if (previous_header && level > previous_header.level) { + unfold(cm, CodeMirror.Pos(previous_header.line, 0)); + } + previous_header = { + line: i, + level: level, + }; + } + } + }); + } + function folding_mode_all(cm) { + cm.operation(function() { + for (let i = cm.firstLine(), e = cm.lastLine(); i <= e; i++) { + if (/header/.test(cm.getTokenTypeAt(CodeMirror.Pos(i, 0))) === true) { + unfold(cm, CodeMirror.Pos(i, 0)); + } + } + }); + } +} + + +/* + * Promote heading or move table column to left. + */ +export const org_metaleft = (cm) => { + const line = cm.getCursor().line; + _metaleft(cm, line); +}; +function _metaleft(cm, line) { + let p = null; + if (p = isTitle(cm, line)) { + if (p["level"] > 1) { + cm.replaceRange( + "", + { line: p.start, ch: 0 }, + { line: p.start, ch: 1 }, + ); + } + } else if (p = isItemList(cm, line)) { + for (let i=p.start; i<=p.end; i++) { + if (p["level"] > 0) { + cm.replaceRange( + "", + { line: i, ch: 0 }, + { line: i, ch: 2 }, + ); + } + } + } else if (p = isNumberedList(cm, line)) { + for (let i=p.start; i<=p.end; i++) { + if (p["level"] > 0) { + cm.replaceRange( + "", + { line: i, ch: 0 }, + { line: i, ch: 3 }, + ); + } + } + rearrange_list(cm, line); + } +} + +/* + * Demote a subtree, a list item or move table column to right. + * In front of a drawer or a block keyword, indent it correctly. + */ +export const org_metaright = (cm) => { + const line = cm.getCursor().line; + _metaright(cm, line); +}; + +function _metaright(cm, line) { + let p = null; + let tmp = null; + if (p = isTitle(cm, line)) { + cm.replaceRange("*", { line: p.start, ch: 0 }); + } else if (p = isItemList(cm, line)) { + if (tmp = isItemList(cm, p.start - 1)) { + if (p.level < tmp.level + 1) { + for (let i=p.start; i<=p.end; i++) { + cm.replaceRange(" ", { line: i, ch: 0 }); + } + } + } + } else if (p = isNumberedList(cm, line)) { + if (tmp = isNumberedList(cm, p.start - 1)) { + if (p.level < tmp.level + 1) { + for (let i=p.start; i<=p.end; i++) { + cm.replaceRange(" ", { line: i, ch: 0 }); + } + rearrange_list(cm, p.start); + } + } + } +} + +/* + * Insert a new heading or wrap a region in a table + */ +export const org_meta_return = (cm) => { + const line = cm.getCursor().line; + const content = cm.getLine(line); + let p = null; + + if (p = isItemList(cm, line)) { + const level = p.level; + cm.replaceRange( + "\n"+" ".repeat(level*2)+"- ", + { line: p.end, ch: cm.getLine(p.end).length }, + ); + cm.setCursor({ line: p.end+1, ch: level*2+2 }); + } else if (p = isNumberedList(cm, line)) { + const level = p.level; + cm.replaceRange( + "\n"+" ".repeat(level*3)+(p.n+1)+". ", + { line: p.end, ch: cm.getLine(p.end).length }, + ); + cm.setCursor({ line: p.end+1, ch: level*3+3 }); + rearrange_list(cm, line); + } else if (p = isTitle(cm, line)) { + const tmp = previousOfType(cm, "title", line); + const level = tmp && tmp.level || 1; + cm.replaceRange("\n"+"*".repeat(level)+" ", { line: line, ch: content.length }); + cm.setCursor({ line: line+1, ch: level+1 }); + } else if (content.trim() === "") { + cm.replaceRange("* ", { line: line, ch: 0 }); + cm.setCursor({ line: line, ch: 2 }); + } else { + cm.replaceRange("\n\n* ", { line: line, ch: content.length }); + cm.setCursor({ line: line + 2, ch: 2 }); + } +}; + + +const TODO_CYCLES = ["TODO", "DONE", ""]; +/* + * Cycle the thing at point or in the current line, depending on context. + * Depending on context, this does one of the following: + * - TODO: switch a timestamp at point one day into the past + * - DONE: on a headline, switch to the previous TODO keyword. + * - TODO: on an item, switch entire list to the previous bullet type + * - TODO: on a property line, switch to the previous allowed value + * - TODO: on a clocktable definition line, move time block into the past + */ +export const org_shiftleft = (cm) => { + const cycles = [].concat(TODO_CYCLES.slice(0).reverse(), TODO_CYCLES.slice(-1)); + const line = cm.getCursor().line; + const content = cm.getLine(line); + const params = isTitle(cm, line); + + if (params === null) return; + params["status"] = cycles[cycles.indexOf(params["status"]) + 1]; + cm.replaceRange( + makeTitle(params), + { line: line, ch: 0 }, + { line: line, ch: content.length }, + ); +}; +/* + * Cycle the thing at point or in the current line, depending on context. + * Depending on context, this does one of the following: + * - TODO: switch a timestamp at point one day into the future + * - DONE: on a headline, switch to the next TODO keyword. + * - TODO: on an item, switch entire list to the next bullet type + * - TODO: on a property line, switch to the next allowed value + * - TODO: on a clocktable definition line, move time block into the future + */ +export const org_shiftright = (cm) => { + cm.operation(() => { + const cycles = [].concat(TODO_CYCLES, [TODO_CYCLES[0]]); + const line = cm.getCursor().line; + const content = cm.getLine(line); + const params = isTitle(cm, line); + + if (params === null) return; + params["status"] = cycles[cycles.indexOf(params["status"]) + 1]; + cm.replaceRange( + makeTitle(params), + { line: line, ch: 0 }, + { line: line, ch: content.length }, + ); + }); +}; + +export const org_insert_todo_heading = (cm) => { + cm.operation(() => { + const line = cm.getCursor().line; + const content = cm.getLine(line); + + let p = null; + if (p = isItemList(cm, line)) { + const level = p.level; + cm.replaceRange( + "\n"+" ".repeat(level*2)+"- [ ] ", + { line: p.end, ch: cm.getLine(p.end).length }, + ); + cm.setCursor({ line: line+1, ch: 6+level*2 }); + } else if (p = isNumberedList(cm, line)) { + const level = p.level; + cm.replaceRange( + "\n"+" ".repeat(level*3)+(p.n+1)+". [ ] ", + { line: p.end, ch: cm.getLine(p.end).length }, + ); + cm.setCursor({ line: p.end+1, ch: level*3+7 }); + rearrange_list(cm, line); + } else if (p = isTitle(cm, line)) { + const level = p && p.level || 1; + cm.replaceRange("\n"+"*".repeat(level)+" TODO ", { line: line, ch: content.length }); + cm.setCursor({ line: line+1, ch: level+6 }); + } else if (content.trim() === "") { + cm.replaceRange("* TODO ", { line: line, ch: 0 }); + cm.setCursor({ line: line, ch: 7 }); + } else { + cm.replaceRange("\n\n* TODO ", { line: line, ch: content.length }); + cm.setCursor({ line: line + 2, ch: 7 }); + } + }); +}; + + +/* + * Move subtree up or move table row up. + * Calls ‘org-move-subtree-up’ or ‘org-table-move-row’ or + * ‘org-move-item-up’, depending on context + */ +export const org_metaup = (cm) => { + cm.operation(() => { + const line = cm.getCursor().line; + let p = null; + + if (p = isItemList(cm, line)) { + const a = isItemList(cm, p.start - 1); + if (a) { + swap(cm, [p.start, p.end], [a.start, a.end]); + rearrange_list(cm, line); + } + } else if (p = isNumberedList(cm, line)) { + const a = isNumberedList(cm, p.start - 1); + if (a) { + swap(cm, [p.start, p.end], [a.start, a.end]); + rearrange_list(cm, line); + } + } else if (p = isTitle(cm, line)) { + let _line = line; + let a; + do { + _line -= 1; + if (a = isTitle(cm, _line, p.level)) { + break; + } + } while (_line > 0); + + if (a) { + swap(cm, [p.start, p.end], [a.start, a.end]); + org_set_fold(cm); + } + } + }); +}; + +/* + * Move subtree down or move table row down. + * Calls ‘org-move-subtree-down’ or ‘org-table-move-row’ or + * ‘org-move-item-down’, depending on context + */ +export const org_metadown = (cm) => { + cm.operation(() => { + const line = cm.getCursor().line; + let p = null; + + if (p = isItemList(cm, line)) { + const a = isItemList(cm, p.end + 1); + if (a) { + swap(cm, [p.start, p.end], [a.start, a.end]); + } + } else if (p = isNumberedList(cm, line)) { + const a = isNumberedList(cm, p.end + 1); + if (a) { + swap(cm, [p.start, p.end], [a.start, a.end]); + } + rearrange_list(cm, line); + } else if (p = isTitle(cm, line)) { + const a = isTitle(cm, p.end + 1, p.level); + if (a) { + swap(cm, [p.start, p.end], [a.start, a.end]); + org_set_fold(cm); + } + } + }); +}; + + +export const org_shiftmetaright = function(cm) { + cm.operation(() => { + const line = cm.getCursor().line; + let p = null; + if (p = isTitle(cm, line)) { + _metaright(cm, line); + for (let i=p.start + 1; i<=p.end; i++) { + if (isTitle(cm, i)) { + _metaright(cm, i); + } + } + } + }); +}; + +export const org_shiftmetaleft = function(cm) { + cm.operation(() => { + const line = cm.getCursor().line; + let p = null; + if (p = isTitle(cm, line)) { + if (p.level === 1) return; + _metaleft(cm, line); + for (let i=p.start + 1; i<=p.end; i++) { + if (isTitle(cm, i)) { + _metaleft(cm, i); + } + } + } + }); +}; + + +function makeTitle(p) { + let content = "*".repeat(p["level"])+" "; + if (p["status"]) { + content += p["status"]+" "; + } + content += p["content"]; + return content; +} + +function previousOfType(cm, type, line) { + let tmp; + let i; + for (i=line - 1; i>0; i--) { + if (type === "list" || type === null) { + tmp = isItemList(cm, line); + } else if (type === "numbered" || type === null) { + tmp = isNumberedList(cm, line); + } else if (type === "title" || type === null) { + tmp = isTitle(cm, line); + } + if (tmp !== null) { + return tmp; + } + } + return null; +} + +function isItemList(cm, line) { + const rootLineItem = findRootLine(cm, line); + if (rootLineItem === null) return null; + line = rootLineItem; + const content = cm.getLine(line); + + if (content && (content.trimLeft()[0] !== "-" || content.trimLeft()[1] !== " ")) return null; + const padding = content.replace(/^(\s*).*$/, "$1").length; + if (padding % 2 !== 0) return null; + return { + type: "list", + level: padding / 2, + content: content.trimLeft().replace(/^\s*\-\s(.*)$/, "$1"), + start: line, + end: function(_cm, _line) { + let line_candidate = _line; + let content = null; + do { + _line += 1; + content = _cm.getLine(_line); + if (content === undefined || content.trimLeft()[0] === "-") { + break; + } else if (/^\s+/.test(content)) { + line_candidate = _line; + continue; + } else { + break; + } + } while (_line <= _cm.lineCount()); + return line_candidate; + }(cm, line), + }; + + function findRootLine(_cm, _line) { + let content; + do { + content = _cm.getLine(_line); + if (/^\s*\-/.test(content)) return _line; + else if (/^\s+/.test(content) === false) { + break; + } + _line -= 1; + } while (_line >= 0); + return null; + } +} +function isNumberedList(cm, line) { + const rootLineItem = findRootLine(cm, line); + if (rootLineItem === null) return null; + line = rootLineItem; + const content = cm.getLine(line); + + if (/^[0-9]+[\.\)]\s.*$/.test(content && content.trimLeft()) === false) return null; + const padding = content.replace(/^(\s*)[0-9]+.*$/, "$1").length; + if (padding % 3 !== 0) return null; + return { + type: "numbered", + level: padding / 3, + content: content.trimLeft().replace(/^[0-9]+[\.\)]\s(.*)$/, "$1"), + start: line, + end: function(_cm, _line) { + let line_candidate = _line; + let content = null; + do { + _line += 1; + content = _cm.getLine(_line); + if (content === undefined || /^[0-9]+[\.\)]/.test(content.trimLeft())) { + break; + } else if (/^\s+/.test(content)) { + line_candidate = _line; + continue; + } else { + break; + } + } while (_line <= _cm.lineCount()); + return line_candidate; + }(cm, line), + // specific + n: parseInt(content.trimLeft().replace(/^([0-9]+).*$/, "$1")), + separator: content.trimLeft().replace(/^[0-9]+([\.\)]).*$/, "$1"), + }; + + function findRootLine(_cm, _line) { + let content; + do { + content = _cm.getLine(_line); + if (/^\s*[0-9]+[\.\)]\s/.test(content)) return _line; + else if (/^\s+/.test(content) === false) { + break; + } + _line -= 1; + } while (_line >= 0); + return null; + } +} +function isTitle(cm, line, level) { + const content = cm.getLine(line); + if (/^\*+\s/.test(content) === false) return null; + const match = content.match(/^(\*+)([\sA-Z]*)\s(.*)$/); + const reference_level = match[1].length; + if (level !== undefined && level !== reference_level) return null; + if (match === null) return null; + return { + type: "title", + level: reference_level, + content: match[3], + start: line, + end: function(_cm, _line) { + let line_candidate = _line; + let content = null; + do { + _line += 1; + content = _cm.getLine(_line); + if (content === undefined) break; + const match = content.match(/^(\*+)\s.*/); + if ( + match && match[1] && + ( match[1].length === reference_level || match[1].length < reference_level) + ) { + break; + } else { + line_candidate = _line; + continue; + } + } while (_line <= _cm.lineCount()); + return line_candidate; + }(cm, line), + // specific + status: match[2].trim(), + }; +} + +function rearrange_list(cm, line) { + const line_inferior = find_limit_inferior(cm, line); + const line_superior = find_limit_superior(cm, line); + + let last_p = null; + let p; + + for (let i=line_inferior; i<=line_superior; i++) { + if (p = isNumberedList(cm, i)) { + // rearrange numbers on the numbered list + if (last_p) { + if (p.level === last_p.level) { + const tmp = findLastAtLevel(cm, p.start, line_inferior, p.level); + if (tmp && p.n !== tmp.n + 1) setNumber(cm, p.start, tmp.n + 1); + } else if (p.level > last_p.level) { + if (p.n !== 1) { + setNumber(cm, p.start, 1); + } + } else if (p.level < last_p.level) { + const tmp = findLastAtLevel(cm, p.start, line_inferior, p.level); + if (tmp && p.n !== tmp.n + 1) setNumber(cm, p.start, tmp.n + 1); + } + } else { + if (p.n !== 1) setNumber(cm, p.start, 1); + } + } + + + if (p = (isNumberedList(cm, i) || isItemList(cm, i))) { + // rearrange spacing levels in list + if (last_p) { + if (p.level > last_p.level) { + if (p.level !== last_p.level + 1) { + setLevel(cm, [p.start, p.end], last_p.level + 1, p.type); + } + } + } else { + if (p.level !== 0) { + setLevel(cm, [p.start, p.end], 0, p.type); + } + } + } + + + last_p = p; + // we can process content block instead of line + if (p) { + i += (p.end - p.start); + } + } + + function findLastAtLevel(_cm, line, line_limit_inf, level) { + let p; + do { + line -= 1; + if ((p = isNumberedList(_cm, line)) && p.level === level) { + return p; + } + } while (line > line_limit_inf); + + return null; + } + + function setLevel(_cm, range, level, type) { + let content; + let i; + for (i=range[0]; i<=range[1]; i++) { + content = cm.getLine(i).trimLeft(); + const n_spaces = function(_level, _line, _type) { + let spaces = _level * 3; + if (_line > 0) { + spaces += _type === "numbered" ? 3 : 2; + } + return spaces; + }(level, i - range[0], type); + + content = " ".repeat(n_spaces) + content; + cm.replaceRange( + content, + { line: i, ch: 0 }, + { line: i, ch: _cm.getLine(i).length }, + ); + } + } + + function setNumber(_cm, line, level) { + const content = _cm.getLine(line); + const new_content = content.replace(/[0-9]+\./, level+"."); + cm.replaceRange( + new_content, + { line: line, ch: 0 }, + { line: line, ch: content.length }, + ); + } + + function find_limit_inferior(_cm, _line) { + let content; + let p; + let match; + let line_candidate = _line; + do { + content = _cm.getLine(_line); + p = isNumberedList(_cm, _line); + match = /(\s+).*$/.exec(content); + if (p) line_candidate = _line; + if (!p || !match) break; + _line -= 1; + } while (_line >= 0); + return line_candidate; + } + function find_limit_superior(_cm, _line) { + let content; + let p; + let match; + let line_candidate = _line; + do { + content = _cm.getLine(_line); + p = isNumberedList(_cm, _line); + match = /(\s+).*$/.exec(content); + if (p) line_candidate = _line; + if (!p || !match) break; + _line += 1; + } while (_line < _cm.lineCount()); + return line_candidate; + } +} + +function swap(cm, from, to) { + const from_content = cm.getRange( + { line: from[0], ch: 0 }, + { line: from[1], ch: cm.getLine(from[1]).length }, + ); + const to_content = cm.getRange( + { line: to[0], ch: 0 }, + { line: to[1], ch: cm.getLine(to[1]).length }, + ); + const cursor = cm.getCursor(); + + if (to[0] > from[0]) { + // moving down + cm.replaceRange( + from_content, + { line: to[0], ch: 0 }, + { line: to[1], ch: cm.getLine(to[1]).length }, + ); + cm.replaceRange( + to_content, + { line: from[0], ch: 0 }, + { line: from[1], ch: cm.getLine(from[1]).length }, + ); + cm.setCursor({ + line: cursor.line + (to[1] - to[0] + 1), + ch: cursor.ch, + }); + } else { + // moving up + cm.replaceRange( + to_content, + { line: from[0], ch: 0 }, + { line: from[1], ch: cm.getLine(from[1]).length }, + ); + cm.replaceRange( + from_content, + { line: to[0], ch: 0 }, + { line: to[1], ch: cm.getLine(to[1]).length }, + ); + cm.setCursor({ + line: cursor.line - (to[1] - to[0] + 1), + ch: cursor.ch, + }); + } +} + +export function fold(cm, start) { + cm.foldCode(start, null, "fold"); +} +export function unfold(cm, start) { + cm.foldCode(start, null, "unfold"); +} +export function isFold(cm, start) { + const line = start.line; + const marks = cm.findMarks(CodeMirror.Pos(line, 0), CodeMirror.Pos(line + 1, 0)); + for (let i = 0; i < marks.length; ++i) { + if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i]; + } + return false; +} diff --git a/public/assets/pages/viewerpage/application_editor/erlang.js b/public/assets/pages/viewerpage/application_editor/erlang.js new file mode 100644 index 00000000..903fb7b3 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/erlang.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/erlang/erlang.js"; +CodeMirror.__mode = "erlang"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/go.js b/public/assets/pages/viewerpage/application_editor/go.js new file mode 100644 index 00000000..9638ff4e --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/go.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/go/go.js"; +CodeMirror.__mode = "go"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/htmlmixed.js b/public/assets/pages/viewerpage/application_editor/htmlmixed.js new file mode 100644 index 00000000..63cc7f4b --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/htmlmixed.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/htmlmixed/htmlmixed.js"; +CodeMirror.__mode = "htmlmixed"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/javascript.js b/public/assets/pages/viewerpage/application_editor/javascript.js new file mode 100644 index 00000000..42c0908d --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/javascript.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/javascript/javascript.js"; +CodeMirror.__mode = "javascript"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/jsx.js b/public/assets/pages/viewerpage/application_editor/jsx.js new file mode 100644 index 00000000..838f6980 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/jsx.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/jsx/jsx.js"; +CodeMirror.__mode = "jsx"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/keymap_base.js b/public/assets/pages/viewerpage/application_editor/keymap_base.js new file mode 100644 index 00000000..591f6f6f --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/keymap_base.js @@ -0,0 +1 @@ +import "../../../lib/vendor/codemirror/keymap/sublime.js"; diff --git a/public/assets/pages/viewerpage/application_editor/keymap_vim.js b/public/assets/pages/viewerpage/application_editor/keymap_vim.js new file mode 100644 index 00000000..a3217562 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/keymap_vim.js @@ -0,0 +1 @@ +import "../../../lib/vendor/codemirror/keymap/vim.js"; diff --git a/public/assets/pages/viewerpage/application_editor/lua.js b/public/assets/pages/viewerpage/application_editor/lua.js new file mode 100644 index 00000000..8d6eef3b --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/lua.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/lua/lua.js"; +CodeMirror.__mode = "lua"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/orgmode.js b/public/assets/pages/viewerpage/application_editor/orgmode.js new file mode 100644 index 00000000..697d393c --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/orgmode.js @@ -0,0 +1,313 @@ +import "../../../lib/vendor/codemirror/addon/mode/simple.js"; +import { + org_cycle, org_shifttab, org_metaleft, org_metaright, org_meta_return, org_metaup, + org_metadown, org_insert_todo_heading, org_shiftleft, org_shiftright, fold, unfold, + isFold, org_set_fold, org_shiftmetaleft, org_shiftmetaright, +} from "./emacs-org.js"; + +// TODO: +// import { pathBuilder, dirname, currentShare } from "../../../helpers/"; + +const CodeMirror = window.CodeMirror; + +CodeMirror.__mode = "orgmode"; + +CodeMirror.defineSimpleMode("orgmode", { + start: [ + { regex: /(\*\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header level1 org-level-star", "header level1 org-todo", "header level1 org-done", "header level1 org-priority", "header level1", "header level1 void", "header level1 comment"] }, + { regex: /(\*{1,}\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header org-level-star", "header org-todo", "header org-done", "header org-priority", "header", "header void", "header comment"] }, + { regex: /(\+[^\+]+\+)/, token: ["strikethrough"] }, + { regex: /(\*[^\*]+\*)/, token: ["strong"] }, + { regex: /(\/[^\/]+\/)/, token: ["em"] }, + { regex: /(\_[^\_]+\_)/, token: ["link"] }, + { regex: /(\~[^\~]+\~)/, token: ["comment"] }, + { regex: /(\=[^\=]+\=)/, token: ["comment"] }, + { regex: /\[\[[^\[\]]+\]\[[^\[\]]+\]\]/, token: "org-url" }, // links + { regex: /\[\[[^\[\]]+\]\]/, token: "org-image" }, // image + { regex: /\[[xX\s\-\_]\]/, token: "qualifier org-toggle" }, // checkbox + { regex: /\#\+(?:(BEGIN|begin))_[a-zA-Z]*/, token: "comment", next: "env", sol: true }, // comments + { regex: /:?[A-Z_]+\:.*/, token: "comment", sol: true }, // property drawers + { regex: /(\#\+[a-zA-Z_]*)(\:.*)/, token: ["keyword", "qualifier"], sol: true }, // environments + { regex: /(CLOCK\:|SHEDULED\:|DEADLINE\:)(\s.+)/, token: ["comment", "keyword"] }, + ], + env: [ + { regex: /\#\+(?:(END|end))_[a-zA-Z]*/, token: "comment", next: "start", sol: true }, + { regex: /.*/, token: "comment" }, + ], +}); +CodeMirror.registerHelper("fold", "orgmode", function(cm, start) { + // init + const levelToMatch = headerLevel(start.line); + + // no folding needed + if (levelToMatch === null) return; + + // find folding limits + const lastLine = cm.lastLine(); + let end = start.line; + while (end < lastLine) { + end += 1; + const level = headerLevel(end); + if (level && level <= levelToMatch) { + end = end - 1; + break; + }; + } + + return { + from: CodeMirror.Pos(start.line, cm.getLine(start.line).length), + to: CodeMirror.Pos(end, cm.getLine(end).length), + }; + + function headerLevel(lineNo) { + const line = cm.getLine(lineNo); + const match = /^\*+/.exec(line); + if (match && match.length === 1 && /header/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0)))) { + return match[0].length; + } + return null; + } +}); +CodeMirror.registerGlobalHelper("fold", "drawer", function(mode) { + return mode.name === "orgmode" ? true : false; +}, function(cm, start) { + const drawer = isBeginningOfADrawer(start.line); + if (drawer === false) return; + + // find folding limits + const lastLine = cm.lastLine(); + let end = start.line; + while (end < lastLine) { + end += 1; + if (isEndOfADrawer(end)) { + break; + } + } + + return { + from: CodeMirror.Pos(start.line, cm.getLine(start.line).length), + to: CodeMirror.Pos(end, cm.getLine(end).length), + }; + + function isBeginningOfADrawer(lineNo) { + const line = cm.getLine(lineNo); + const match = /^\:.*\:$/.exec(line); + if (match && match.length === 1 && match[0] !== ":END:") { + return true; + } + return false; + } + function isEndOfADrawer(lineNo) { + const line = cm.getLine(lineNo); + return line.trim() === ":END:" ? true : false; + } +}); + + +CodeMirror.registerHelper("orgmode", "init", (editor, fn) => { + editor.setOption("extraKeys", { + "Tab": (cm) => org_cycle(cm), + "Shift-Tab": (cm) => fn("shifttab", org_shifttab(cm)), + "Alt-Left": (cm) => org_metaleft(cm), + "Alt-Right": (cm) => org_metaright(cm), + "Alt-Enter": (cm) => org_meta_return(cm), + "Alt-Up": (cm) => org_metaup(cm), + "Alt-Down": (cm) => org_metadown(cm), + "Shift-Alt-Left": (cm) => org_shiftmetaleft(cm), + "Shift-Alt-Right": (cm) => org_shiftmetaright(cm), + "Shift-Alt-Enter": (cm) => org_insert_todo_heading(cm), + "Shift-Left": (cm) => org_shiftleft(cm), + "Shift-Right": (cm) => org_shiftright(cm), + }); + fn("shifttab", org_set_fold(editor)); + + editor.on("mousedown", toggleHandler); + editor.on("touchstart", toggleHandler); + editor.on("gutterClick", foldLine); + + // fold everything except headers by default + editor.operation(function() { + for (let i = 0; i < editor.lineCount(); i++) { + if (/header/.test(editor.getTokenTypeAt(CodeMirror.Pos(i, 0))) === false) { + fold(editor, CodeMirror.Pos(i, 0)); + } + } + }); + return CodeMirror.orgmode.destroy.bind(this, editor); +}); + +CodeMirror.registerHelper("orgmode", "destroy", (editor) => { + editor.off("mousedown", toggleHandler); + editor.off("touchstart", toggleHandler); + editor.off("gutterClick", foldLine); +}); + +function foldLine(cm, line) { + const cursor = { line: line, ch: 0 }; + isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor); +} + + +let widgets = []; +function toggleHandler(cm, e) { + const position = cm.coordsChar({ + left: e.clientX || (e.targetTouches && e.targetTouches[0].clientX), + top: e.clientY || (e.targetTouches && e.targetTouches[0].clientY), + }, "page"); + const token = cm.getTokenAt(position); + + _disableSelection(); + if (/org-level-star/.test(token.type)) { + _preventIfShould(); + _foldHeadline(); + _disableSelection(); + } else if (/org-toggle/.test(token.type)) { + _preventIfShould(); + _toggleCheckbox(); + _disableSelection(); + } else if (/org-todo/.test(token.type)) { + _preventIfShould(); + _toggleTodo(); + _disableSelection(); + } else if (/org-done/.test(token.type)) { + _preventIfShould(); + _toggleDone(); + _disableSelection(); + } else if (/org-priority/.test(token.type)) { + _preventIfShould(); + _togglePriority(); + _disableSelection(); + } else if (/org-url/.test(token.type)) { + _disableSelection(); + _navigateLink(); + } else if (/org-image/.test(token.type)) { + _disableSelection(); + _toggleImageWidget(); + } + + function _preventIfShould() { + if ("ontouchstart" in window) e.preventDefault(); + } + function _disableSelection() { + cm.on("beforeSelectionChange", _onSelectionChangeHandler); + function _onSelectionChangeHandler(cm, obj) { + obj.update([{ + anchor: position, + head: position, + }]); + cm.off("beforeSelectionChange", _onSelectionChangeHandler); + } + } + + function _foldHeadline() { + const line = position.line; + if (line >= 0) { + const cursor = { line: line, ch: 0 }; + isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor); + } + } + + function _toggleCheckbox() { + const line = position.line; + const content = cm.getRange( + { line: line, ch: token.start }, + { line: line, ch: token.end }, + ); + const new_content = content === "[X]" || content === "[x]" ? "[ ]" : "[X]"; + cm.replaceRange( + new_content, + { line: line, ch: token.start }, + { line: line, ch: token.end }, + ); + } + + function _toggleTodo() { + const line = position.line; + cm.replaceRange( + "DONE", + { line: line, ch: token.start }, + { line: line, ch: token.end }, + ); + } + + function _toggleDone() { + const line = position.line; + cm.replaceRange( + "TODO", + { line: line, ch: token.start }, + { line: line, ch: token.end }, + ); + } + + function _togglePriority() { + const PRIORITIES = [" [#A] ", " [#B] ", " [#C] ", " [#A] "]; + const line = position.line; + const content = cm.getRange({ line: line, ch: token.start }, { line: line, ch: token.end }); + const new_content = PRIORITIES[PRIORITIES.indexOf(content) + 1]; + cm.replaceRange( + new_content, + { line: line, ch: token.start }, + { line: line, ch: token.end }, + ); + } + + function _toggleImageWidget() { + const exist = !!widgets + .filter((line) => line === position.line)[0]; + + if (exist === false) { + if (!token.string.match(/\[\[(.*)\]\]/)) return null; + const $node = _buildImage(RegExp.$1); + const widget = cm.addLineWidget(position.line, $node, { coverGutter: false }); + widgets.push(position.line); + $node.addEventListener("click", closeWidget); + + function closeWidget() { + widget.clear(); + $node.removeEventListener("click", closeWidget); + widgets = widgets.filter((line) => line !== position.line); + } + } + function _buildImage(src) { + const $el = document.createElement("div"); + const $img = document.createElement("img"); + + if (/^https?\:\/\//.test(src)) { + $img.src = src; + } else { + const root_path = dirname(window.location.pathname.replace(/^\/view/, "")); + const img_path = src; + $img.src = "/api/files/cat?path="+encodeURIComponent(pathBuilder(root_path, img_path)); + } + $el.appendChild($img); + return $el; + } + return null; + } + + function _navigateLink() { + token.string.match(/\[\[(.*?)\]\[/); + const link = RegExp.$1; + if (!link) return; + + let open = "_blank"; + const isMobile = screen.availWidth < screen.availHeight; + if (!document.querySelector(".component_fab img.component_icon[alt=\"save\"]")) { + open = "_self"; + } else if (isMobile) { + open = "_self"; + } + + if (/^https?\:\/\//.test(link)) { + window.open(link, open); + } else { + const root_path = dirname(window.location.pathname.replace(/^\/view/, "")); + const share = currentShare(); + const url = share ? "/view"+pathBuilder(root_path, link)+"?share="+share : "/view"+pathBuilder(root_path, link); + window.open(url, open); + } + } +} + + +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/perl.js b/public/assets/pages/viewerpage/application_editor/perl.js new file mode 100644 index 00000000..adcf5dc2 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/perl.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/perl/perl.js"; +CodeMirror.__mode = "perl"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/php.js b/public/assets/pages/viewerpage/application_editor/php.js new file mode 100644 index 00000000..05302d03 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/php.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/php/php.js"; +CodeMirror.__mode = "php"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/properties.js b/public/assets/pages/viewerpage/application_editor/properties.js new file mode 100644 index 00000000..c308255f --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/properties.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/properties/properties.js"; +CodeMirror.__mode = "properties"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/python.js b/public/assets/pages/viewerpage/application_editor/python.js new file mode 100644 index 00000000..a50cca82 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/python.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/python/python.js"; +CodeMirror.__mode = "python"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/r.js b/public/assets/pages/viewerpage/application_editor/r.js new file mode 100644 index 00000000..b1bb546e --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/r.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/r/r.js"; +CodeMirror.__mode = "r"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/ruby.js b/public/assets/pages/viewerpage/application_editor/ruby.js new file mode 100644 index 00000000..6917a4fc --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/ruby.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/ruby/ruby.js"; +CodeMirror.__mode = "ruby"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/rust.js b/public/assets/pages/viewerpage/application_editor/rust.js new file mode 100644 index 00000000..c271b8fa --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/rust.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/rust/rust.js"; +CodeMirror.__mode = "rust"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/sass.js b/public/assets/pages/viewerpage/application_editor/sass.js new file mode 100644 index 00000000..342af3e3 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/sass.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/sass/sass.js"; +CodeMirror.__mode = "sass"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/shell.js b/public/assets/pages/viewerpage/application_editor/shell.js new file mode 100644 index 00000000..5da71d5a --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/shell.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/shell/shell.js"; +CodeMirror.__mode = "shell"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/sparql.js b/public/assets/pages/viewerpage/application_editor/sparql.js new file mode 100644 index 00000000..c780585d --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/sparql.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/sparql/sparql.js"; +CodeMirror.__mode = "sparql"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/spreadsheet.js b/public/assets/pages/viewerpage/application_editor/spreadsheet.js new file mode 100644 index 00000000..a2293c46 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/spreadsheet.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/spreadsheet/spreadsheet.js"; +CodeMirror.__mode = "spreadsheet"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/sql.js b/public/assets/pages/viewerpage/application_editor/sql.js new file mode 100644 index 00000000..eff773a6 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/sql.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/sql/sql.js"; +CodeMirror.__mode = "sql"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/stex.js b/public/assets/pages/viewerpage/application_editor/stex.js new file mode 100644 index 00000000..9104d267 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/stex.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/stex/stex.js"; +CodeMirror.__mode = "stex"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/text.js b/public/assets/pages/viewerpage/application_editor/text.js new file mode 100644 index 00000000..a4c29d3f --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/text.js @@ -0,0 +1 @@ +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/xml.js b/public/assets/pages/viewerpage/application_editor/xml.js new file mode 100644 index 00000000..a17cf9d1 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/xml.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/xml/xml.js"; +CodeMirror.__mode = "xml"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/yaml-frontmatter.js b/public/assets/pages/viewerpage/application_editor/yaml-frontmatter.js new file mode 100644 index 00000000..414cc5ee --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/yaml-frontmatter.js @@ -0,0 +1,4 @@ +import "../../../lib/vendor/codemirror/mode/gfm/gfm.js"; +import "../../../lib/vendor/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js"; +CodeMirror.__mode = "yaml-frontmatter"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor/yaml.js b/public/assets/pages/viewerpage/application_editor/yaml.js new file mode 100644 index 00000000..27031728 --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor/yaml.js @@ -0,0 +1,3 @@ +import "../../../lib/vendor/codemirror/mode/yaml/yaml.js"; +CodeMirror.__mode = "yaml"; +export default CodeMirror; diff --git a/public/assets/pages/viewerpage/application_editor_orgmode.js b/public/assets/pages/viewerpage/application_editor_orgmode.js new file mode 100644 index 00000000..14ffe45b --- /dev/null +++ b/public/assets/pages/viewerpage/application_editor_orgmode.js @@ -0,0 +1,8 @@ +export default function(editor) { + CodeMirror.orgmode.init(editor, (key, value) => { + if (key === "shifttab") { + // org_shifttab(this.state.editor) + // this.props.onFoldChange(value); + } + }); +} diff --git a/public/assets/pages/viewerpage/application_video.js b/public/assets/pages/viewerpage/application_video.js index fd7791fd..90ae10a6 100644 --- a/public/assets/pages/viewerpage/application_video.js +++ b/public/assets/pages/viewerpage/application_video.js @@ -167,6 +167,9 @@ export default function(render, { mime }) { hls.attachMedia($video); }), rxjs.mergeMap(() => rxjs.fromEvent($video, "loadeddata")), + // rxjs.tap(() => renderMenubar(buildMenubar( + // menubarDownload(), + // ))), rxjs.mergeMap(() => { const $loader = qs($page, ".loader"); $loader.replaceChildren(createElement(``)); @@ -177,9 +180,6 @@ export default function(render, { mime }) { setSeek(0); return rxjs.fromEvent($loader, "click").pipe(rxjs.mapTo($loader)); }), - // rxjs.tap(() => renderMenubar(buildMenubar( - // menubarDownload(), - // ))), rxjs.tap(($loader) => { $loader.classList.add("hidden") const $control = qs($page, ".videoplayer_control"); diff --git a/public/assets/pages/viewerpage/common.js b/public/assets/pages/viewerpage/common.js index 4d5f1d2c..25934586 100644 --- a/public/assets/pages/viewerpage/common.js +++ b/public/assets/pages/viewerpage/common.js @@ -16,7 +16,7 @@ export function getFile$() { export function saveFile$() { return rxjs.pipe( rxjs.delay(2000), - rxjs.tap((content) => console.log("SAVED", content)), + rxjs.tap((content) => console.log("SAVED")), ); } diff --git a/public/assets/pages/viewerpage/model_files.js b/public/assets/pages/viewerpage/model_files.js new file mode 100644 index 00000000..409672d6 --- /dev/null +++ b/public/assets/pages/viewerpage/model_files.js @@ -0,0 +1,9 @@ +import rxjs from "../../lib/rx.js"; +import ajax from "../../lib/ajax.js"; + +export function fileOptions(path) { + return ajax({ + url: `/api/files/cat?path=${path}`, + method: "OPTIONS", + }).pipe(rxjs.map((res) => res.responseHeaders.allow.replace(/\r/, "").split(", "))); +}