summaryrefslogtreecommitdiff
path: root/public
diff options
context:
space:
mode:
Diffstat (limited to 'public')
-rw-r--r--public/css/index.css18
-rw-r--r--public/css/markdown.css5
-rw-r--r--public/css/slide.css3
-rw-r--r--public/docs/features.md11
-rw-r--r--public/docs/release-notes.md105
-rw-r--r--public/js/google-drive-picker.js118
-rw-r--r--public/js/google-drive-upload.js267
-rw-r--r--public/js/index.js104
-rw-r--r--public/js/lib/common/constant.ejs2
-rw-r--r--public/js/lib/config/index.js2
-rw-r--r--public/js/lib/editor/ui-elements.js2
-rw-r--r--public/vendor/md-toc.js3
-rw-r--r--public/views/hackmd/header.ejs12
-rw-r--r--public/views/shared/disqus.ejs5
-rw-r--r--public/views/shared/ga.ejs6
-rw-r--r--public/views/shared/help-modal.ejs4
16 files changed, 138 insertions, 529 deletions
diff --git a/public/css/index.css b/public/css/index.css
index b00eba41..3f391e27 100644
--- a/public/css/index.css
+++ b/public/css/index.css
@@ -156,6 +156,10 @@ body.night{
left: 50%;
transform: translate(-50%, -50%);
}
+.night .ui-edit-area .ui-sync-toggle {
+ box-shadow: 2px 0px 2px #353535;
+}
+
.ui-edit-area .ui-sync-toggle:active {
box-shadow: inset 0 3px 5px rgba(0,0,0,.125), 2px 0px 2px #e7e7e7;
}
@@ -292,6 +296,13 @@ body.night{
background: #222;
}
+.night .modal-content,
+.night .panel,
+.night .panel-heading {
+ color: #eee;
+ background-color: #333;
+}
+
.dropdown-menu.CodeMirror-other-cursor {
transition: none;
}
@@ -340,7 +351,8 @@ div[contenteditable]:empty:not(:focus):before{
background: inherit;
}
-.night .navbar .btn-default{
+.night .navbar .btn-default,
+.night .close {
background-color: #333;
border-color: #565656;
color: #eee;
@@ -372,8 +384,10 @@ div[contenteditable]:empty:not(:focus):before{
.night .btn.focus,
.night .btn:focus,
-.night .btn:hover{
+.night .btn:hover,
+.night .close {
color: #fff;
+ background-color: #333;
}
.info-label {
diff --git a/public/css/markdown.css b/public/css/markdown.css
index eaa9ab5c..85a4c594 100644
--- a/public/css/markdown.css
+++ b/public/css/markdown.css
@@ -13,6 +13,10 @@
border: inherit !important;
}
+.night .markdown-body pre {
+ filter: invert(100%);
+}
+
.markdown-body code {
color: inherit !important;
}
@@ -78,6 +82,7 @@
.markdown-body code[data-gist-id] {
background: none;
padding: 0;
+ filter: invert(100%);
}
.markdown-body code[data-gist-id]:before {
diff --git a/public/css/slide.css b/public/css/slide.css
index a8591108..f8f9c717 100644
--- a/public/css/slide.css
+++ b/public/css/slide.css
@@ -81,7 +81,8 @@
.task-list-item-checkbox {
font-size: inherit;
height: 1em;
- margin: 0.2em 0 0.2em -0.65em !important;
+ transform: scale(2);
+ margin: 0.15em 0 0.15em -0.84em !important;
}
pre code .wrapper {
diff --git a/public/docs/features.md b/public/docs/features.md
index 01340fd7..dc6ddafa 100644
--- a/public/docs/features.md
+++ b/public/docs/features.md
@@ -8,7 +8,7 @@ This means that you can write notes with other people on your **desktop**, **tab
You can sign-in via multiple auth providers like **Facebook**, **Twitter**, **GitHub** and many more on the [_homepage_](/).
If you experience any _issues_, feel free to report it on [**GitHub**](https://github.com/hackmdio/hackmd/issues).
-Or meet us on [**Gitter**](https://gitter.im/hackmdio/hackmd) for dev-talk and interactive help.
+Or meet us on [**Matrix.org**](https://riot.im/app/#/room/#hackmd:matrix.org) or [**Gitter**](https://gitter.im/hackmdio/hackmd) for dev-talk and interactive help.
**Thank you very much!**
Workspace
@@ -25,11 +25,16 @@ Workspace
<i class="fa fa-toggle-on fa-fw"></i> View: See only the result.
<i class="fa fa-toggle-off fa-fw"></i> Edit: See only the editor.
+## Night Mode:
+When you are tired of a white screen and like a night mode, click on the little moon <i class="fa fa-moon-o"></i> and turn on the night view of HackMD.
+
+The editor view, which is in night mode by default, can also be toggled between night and day view using the the little sun<i class="fa fa-sun-o fa-fw"></i>.
+
## Image Upload:
You can upload an image simply by clicking on the camera button <i class="fa fa-camera"></i>.
Alternatively, you can **drag-n-drop** an image into the editor. Even **pasting** images is possible!
-This will automatically upload the image to **[imgur](http://imgur.com)**, nothing to worry. :tada:
-![](https://i.imgur.com/9cgQVqD.png)
+This will automatically upload the image to **[imgur](http://imgur.com)**, **[Amazon S3](https://aws.amazon.com/s3/)**, **[Minio](https://minio.io)** or **local filesystem**, nothing to worry about. :tada:
+![imgur](https://i.imgur.com/9cgQVqD.png)
## Share Notes:
If you want to share an **editable** note, just copy the URL.
diff --git a/public/docs/release-notes.md b/public/docs/release-notes.md
index 70510b19..891c506a 100644
--- a/public/docs/release-notes.md
+++ b/public/docs/release-notes.md
@@ -1,6 +1,91 @@
Release Notes
===
+<i class="fa fa-tag"></i> 1.1.1-ce <i class="fa fa-clock-o"></i> 2018-05-23 12:00
+---
+
+### Security
+* Fix Google Drive integration leaked `clientSecret` for Google integration
+* Update base64url package
+
+### Fixes
+* Fix typos in integrations
+* Fix high need of file descriptors during build
+* Fix heroku deployment by limiting node version to <10.x
+
+### Refactors
+* Refactor letterAvatars to be compliant with CSP
+
+### Removes
+* Google Drive integration
+
+### Honorable mentions
+* [Max Wu (jackycute)](https://github.com/jackycute)
+
+<i class="fa fa-tag"></i> 1.1.0-ce <i class="fa fa-clock-o"></i> 2018-04-06 12:00
+---
+
+### Security
+* Adding CSP headers
+* Prevent data-leak by wrong LDAP config
+* Generate dynamic `sessionSecret` if none is specified
+
+### Enhancements
+* Add Minio support
+* Allow posting content to new notes by API
+* Add anonymous edit function in restricted mode
+* Add support for more Mimetypes on S3, Minio and local filesystem uploads
+* Add basic CLI tooling for local user management
+* Add referrer policy
+* Add more usable HTML5 tags
+* Add `useridField` in LDAP config
+* Add option for ReportURI for CSP violations
+* Add persistance for night mode
+* Allow setting of `sessionSecret` by environment variable
+* Add night mode to features page
+* Add Riot / Matrix - Community link to help page
+
+### Fixes
+* Fix ToDo-toggle function
+* Fix LDAP provider name in front-end
+* Fix errors on authenticated sessions for deleted users
+* Fix typo in database migration
+* Fix possible data truncation of authorship
+* Minor fixes in README.md
+* Allow usage of ESC-key by codemirror
+* Fix array of emails in LDAP
+* Fix type errors by environment configs
+* Fix error message on some file API errors
+* Fix minor CSS issues in night mode
+
+### Refactors
+* Refactor contact
+* Refactor social media integration on main page
+* Refactor socket.io code to no longer use referrer
+* Refactor webpack config to need less dependencies in package.json
+* Refactor imageRouter for modularity
+* Refactor configs to be camel case
+
+### Removes
+* Remove unused `tokenSecret` from LDAP config
+
+### Deprecations
+* All non-camelcase config
+
+### Honorable mentions
+* [Dario Ernst (Nebukadneza)](https://github.com/Nebukadneza)
+* [David Mehren (davidmehren)](https://github.com/davidmehren)
+* [Dustin Frisch (fooker)](https://github.com/fooker)
+* [Felix Schäfer (thegcat)](https://github.com/thegcat)
+* [Literallie (xxyy)](https://github.com/xxyy)
+* [Marc Deop (marcdeop)](https://github.com/marcdeop)
+* [Max Wu (jackycute)](https://github.com/jackycute)
+* [Robin Naundorf (senk)](https://github.com/senk)
+* [Stefan Bühler (stbuehler)](https://github.com/stbuehler)
+* [Takeaki Matsumoto (takmatsu)](https://github.com/takmatsu)
+* [Tang TsungYi (vazontang)](https://github.com/vazontang)
+* [Zearin (Zearin)](https://github.com/Zearin)
+
<i class="fa fa-tag"></i> 1.0.1-ce <i class="fa fa-clock-o"></i> 2018-01-19 15:00
---
@@ -46,7 +131,7 @@ Release Notes
* Fix mermaid compatiblity with new version
* Fix SSL CA path parsing
-### Refactor
+### Refactors
* Refactor main page
* Refactor status pages
* Refactor config handling
@@ -182,7 +267,7 @@ Release Notes
* Fix client socket on delete event might not delete corresponding history record correctly
* Fix to handle name or color is undefined error
* Fix history item event not bind properly on pagination change
-* Fix history time should save in UNIX timestamp to avoid time offset issue
+* Fix history time should save in UNIX timestamp to avoid time offset issue
### Removes
- Drop bower the package manager
@@ -230,16 +315,16 @@ Release Notes
### Fixes
* Fix README and features document format and grammar issues
* Fix some potential memory leaks bugs
-* Fix history storage might not fallback correctly
+* Fix history storage might not fallback correctly
* Fix to make mathjax expression display in editor correctly (not italic)
-* Fix note title might have unstriped html tags
+* Fix note title might have unstriped html tags
* Fix client reconnect should resend last operation
* Fix a bug when setting both maxAge and expires may cause user can't signin
* Fix text complete extra tags for blockquote and referrals
* Fix bug that when window close will make ajax fail and cause cookies set to wrong state
* Fix markdown render might fall into regex infinite loop
-* Fix syntax error caused by element contain special characters
-* Fix reference error caused by some scripts loading order
+* Fix syntax error caused by element contain special characters
+* Fix reference error caused by some scripts loading order
* Fix ToC id naming to avoid possible overlap with user ToC
* Fix header nav bar rwd detect element should use div tag or it might glitch the layout
* Fix textcomplete of extra tags for blockquote not match space character in the between
@@ -279,7 +364,7 @@ Release Notes
### Fixes
* Workaround vim mode might overwrite copy keyMap on Windows
* Fix TOC might not update after changeMode
-* Workaround slide mode gets glitch and blurry text on Firefox 47+
+* Workaround slide mode gets glitch and blurry text on Firefox 47+
* Fix idle.js not change isAway property on onAway and onAwayBack events
* Fix http body request entity too large issue
* Fix google-diff-match-patch encodeURI exception issue
@@ -287,8 +372,8 @@ Release Notes
* Fix spellcheck settings from cookies might not a boolean in string type
* Fix cookies might not in boolean type cause page refresh loop
* Fix the signin and logout redirect url might be empty
-* Fix realtime might not clear or remove invalid sockets in queue
-* Fix slide not refresh layout on ajax item loaded
+* Fix realtime might not clear or remove invalid sockets in queue
+* Fix slide not refresh layout on ajax item loaded
* Fix retryOnDisconnect not clean up after reconnected
* Fix some potential memory leaks
@@ -342,7 +427,7 @@ Release Notes
* Support maintenance mode and gracefully exit process on signal
* Update to update doc in db when doc in filesystem have newer modified time
* Update to replace animation acceleration library from gsap to velocity
-* Support image syntax with size
+* Support image syntax with size
* Update textcomplete rules to support more conditions
* Update to use bigger user profile image
* Support showing signin button only when needed
diff --git a/public/js/google-drive-picker.js b/public/js/google-drive-picker.js
deleted file mode 100644
index 5006cd25..00000000
--- a/public/js/google-drive-picker.js
+++ /dev/null
@@ -1,118 +0,0 @@
-/** !
- * Google Drive File Picker Example
- * By Daniel Lo Nigro (http://dan.cx/)
- */
-(function () {
- /**
- * Initialise a Google Driver file picker
- */
- var FilePicker = window.FilePicker = function (options) {
- // Config
- this.apiKey = options.apiKey
- this.clientId = options.clientId
-
- // Elements
- this.buttonEl = options.buttonEl
-
- // Events
- this.onSelect = options.onSelect
- this.buttonEl.on('click', this.open.bind(this))
-
- // Disable the button until the API loads, as it won't work properly until then.
- this.buttonEl.prop('disabled', true)
-
- // Load the drive API
- window.gapi.client.setApiKey(this.apiKey)
- window.gapi.client.load('drive', 'v2', this._driveApiLoaded.bind(this))
- window.google.load('picker', '1', { callback: this._pickerApiLoaded.bind(this) })
- }
-
- FilePicker.prototype = {
- /**
- * Open the file picker.
- */
- open: function () {
- // Check if the user has already authenticated
- var token = window.gapi.auth.getToken()
- if (token) {
- this._showPicker()
- } else {
- // The user has not yet authenticated with Google
- // We need to do the authentication before displaying the Drive picker.
- this._doAuth(false, function () { this._showPicker() }.bind(this))
- }
- },
-
- /**
- * Show the file picker once authentication has been done.
- * @private
- */
- _showPicker: function () {
- var accessToken = window.gapi.auth.getToken().access_token
- var view = new window.google.picker.DocsView()
- view.setMimeTypes('text/markdown,text/html')
- view.setIncludeFolders(true)
- view.setOwnedByMe(true)
- this.picker = new window.google.picker.PickerBuilder()
- .enableFeature(window.google.picker.Feature.NAV_HIDDEN)
- .addView(view)
- .setAppId(this.clientId)
- .setOAuthToken(accessToken)
- .setCallback(this._pickerCallback.bind(this))
- .build()
- .setVisible(true)
- },
-
- /**
- * Called when a file has been selected in the Google Drive file picker.
- * @private
- */
- _pickerCallback: function (data) {
- if (data[window.google.picker.Response.ACTION] === window.google.picker.Action.PICKED) {
- var file = data[window.google.picker.Response.DOCUMENTS][0]
- var id = file[window.google.picker.Document.ID]
- var request = window.gapi.client.drive.files.get({
- fileId: id
- })
- request.execute(this._fileGetCallback.bind(this))
- }
- },
- /**
- * Called when file details have been retrieved from Google Drive.
- * @private
- */
- _fileGetCallback: function (file) {
- if (this.onSelect) {
- this.onSelect(file)
- }
- },
-
- /**
- * Called when the Google Drive file picker API has finished loading.
- * @private
- */
- _pickerApiLoaded: function () {
- this.buttonEl.prop('disabled', false)
- },
-
- /**
- * Called when the Google Drive API has finished loading.
- * @private
- */
- _driveApiLoaded: function () {
- this._doAuth(true)
- },
-
- /**
- * Authenticate with Google Drive via the Google JavaScript API.
- * @private
- */
- _doAuth: function (immediate, callback) {
- window.gapi.auth.authorize({
- client_id: this.clientId,
- scope: 'https://www.googleapis.com/auth/drive.readonly',
- immediate: immediate
- }, callback || function () {})
- }
- }
-}())
diff --git a/public/js/google-drive-upload.js b/public/js/google-drive-upload.js
deleted file mode 100644
index 6c0e8a62..00000000
--- a/public/js/google-drive-upload.js
+++ /dev/null
@@ -1,267 +0,0 @@
-/* eslint-env browser, jquery */
-/**
- * Helper for implementing retries with backoff. Initial retry
- * delay is 1 second, increasing by 2x (+jitter) for subsequent retries
- *
- * @constructor
- */
-var RetryHandler = function () {
- this.interval = 1000 // Start at one second
- this.maxInterval = 60 * 1000 // Don't wait longer than a minute
-}
-
-/**
- * Invoke the function after waiting
- *
- * @param {function} fn Function to invoke
- */
-RetryHandler.prototype.retry = function (fn) {
- setTimeout(fn, this.interval)
- this.interval = this.nextInterval_()
-}
-
-/**
- * Reset the counter (e.g. after successful request.)
- */
-RetryHandler.prototype.reset = function () {
- this.interval = 1000
-}
-
-/**
- * Calculate the next wait time.
- * @return {number} Next wait interval, in milliseconds
- *
- * @private
- */
-RetryHandler.prototype.nextInterval_ = function () {
- var interval = this.interval * 2 + this.getRandomInt_(0, 1000)
- return Math.min(interval, this.maxInterval)
-}
-
-/**
- * Get a random int in the range of min to max. Used to add jitter to wait times.
- *
- * @param {number} min Lower bounds
- * @param {number} max Upper bounds
- * @private
- */
-RetryHandler.prototype.getRandomInt_ = function (min, max) {
- return Math.floor(Math.random() * (max - min + 1) + min)
-}
-
-/**
- * Helper class for resumable uploads using XHR/CORS. Can upload any Blob-like item, whether
- * files or in-memory constructs.
- *
- * @example
- * var content = new Blob(["Hello world"], {"type": "text/plain"});
- * var uploader = new MediaUploader({
- * file: content,
- * token: accessToken,
- * onComplete: function(data) { ... }
- * onError: function(data) { ... }
- * });
- * uploader.upload();
- *
- * @constructor
- * @param {object} options Hash of options
- * @param {string} options.token Access token
- * @param {blob} options.file Blob-like item to upload
- * @param {string} [options.fileId] ID of file if replacing
- * @param {object} [options.params] Additional query parameters
- * @param {string} [options.contentType] Content-type, if overriding the type of the blob.
- * @param {object} [options.metadata] File metadata
- * @param {function} [options.onComplete] Callback for when upload is complete
- * @param {function} [options.onProgress] Callback for status for the in-progress upload
- * @param {function} [options.onError] Callback if upload fails
- */
-var MediaUploader = function (options) {
- var noop = function () {}
- this.file = options.file
- this.contentType = options.contentType || this.file.type || 'application/octet-stream'
- this.metadata = options.metadata || {
- 'title': this.file.name,
- 'mimeType': this.contentType
- }
- this.token = options.token
- this.onComplete = options.onComplete || noop
- this.onProgress = options.onProgress || noop
- this.onError = options.onError || noop
- this.offset = options.offset || 0
- this.chunkSize = options.chunkSize || 0
- this.retryHandler = new RetryHandler()
-
- this.url = options.url
- if (!this.url) {
- var params = options.params || {}
- params.uploadType = 'resumable'
- this.url = this.buildUrl_(options.fileId, params, options.baseUrl)
- }
- this.httpMethod = options.fileId ? 'PUT' : 'POST'
-}
-
-/**
- * Initiate the upload.
- */
-MediaUploader.prototype.upload = function () {
- var xhr = new XMLHttpRequest()
-
- xhr.open(this.httpMethod, this.url, true)
- xhr.setRequestHeader('Authorization', 'Bearer ' + this.token)
- xhr.setRequestHeader('Content-Type', 'application/json')
- xhr.setRequestHeader('X-Upload-Content-Length', this.file.size)
- xhr.setRequestHeader('X-Upload-Content-Type', this.contentType)
-
- xhr.onload = function (e) {
- if (e.target.status < 400) {
- var location = e.target.getResponseHeader('Location')
- this.url = location
- this.sendFile_()
- } else {
- this.onUploadError_(e)
- }
- }.bind(this)
- xhr.onerror = this.onUploadError_.bind(this)
- xhr.send(JSON.stringify(this.metadata))
-}
-
-/**
- * Send the actual file content.
- *
- * @private
- */
-MediaUploader.prototype.sendFile_ = function () {
- var content = this.file
- var end = this.file.size
-
- if (this.offset || this.chunkSize) {
- // Only bother to slice the file if we're either resuming or uploading in chunks
- if (this.chunkSize) {
- end = Math.min(this.offset + this.chunkSize, this.file.size)
- }
- content = content.slice(this.offset, end)
- }
-
- var xhr = new XMLHttpRequest()
- xhr.open('PUT', this.url, true)
- xhr.setRequestHeader('Content-Type', this.contentType)
- xhr.setRequestHeader('Content-Range', 'bytes ' + this.offset + '-' + (end - 1) + '/' + this.file.size)
- xhr.setRequestHeader('X-Upload-Content-Type', this.file.type)
- if (xhr.upload) {
- xhr.upload.addEventListener('progress', this.onProgress)
- }
- xhr.onload = this.onContentUploadSuccess_.bind(this)
- xhr.onerror = this.onContentUploadError_.bind(this)
- xhr.send(content)
-}
-
-/**
- * Query for the state of the file for resumption.
- *
- * @private
- */
-MediaUploader.prototype.resume_ = function () {
- var xhr = new XMLHttpRequest()
- xhr.open('PUT', this.url, true)
- xhr.setRequestHeader('Content-Range', 'bytes */' + this.file.size)
- xhr.setRequestHeader('X-Upload-Content-Type', this.file.type)
- if (xhr.upload) {
- xhr.upload.addEventListener('progress', this.onProgress)
- }
- xhr.onload = this.onContentUploadSuccess_.bind(this)
- xhr.onerror = this.onContentUploadError_.bind(this)
- xhr.send()
-}
-
-/**
- * Extract the last saved range if available in the request.
- *
- * @param {XMLHttpRequest} xhr Request object
- */
-MediaUploader.prototype.extractRange_ = function (xhr) {
- var range = xhr.getResponseHeader('Range')
- if (range) {
- this.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1
- }
-}
-
-/**
- * Handle successful responses for uploads. Depending on the context,
- * may continue with uploading the next chunk of the file or, if complete,
- * invokes the caller's callback.
- *
- * @private
- * @param {object} e XHR event
- */
-MediaUploader.prototype.onContentUploadSuccess_ = function (e) {
- if (e.target.status === 200 || e.target.status === 201) {
- this.onComplete(e.target.response)
- } else if (e.target.status === 308) {
- this.extractRange_(e.target)
- this.retryHandler.reset()
- this.sendFile_()
- } else {
- this.onContentUploadError_(e)
- }
-}
-
-/**
- * Handles errors for uploads. Either retries or aborts depending
- * on the error.
- *
- * @private
- * @param {object} e XHR event
- */
-MediaUploader.prototype.onContentUploadError_ = function (e) {
- if (e.target.status && e.target.status < 500) {
- this.onError(e.target.response)
- } else {
- this.retryHandler.retry(this.resume_.bind(this))
- }
-}
-
-/**
- * Handles errors for the initial request.
- *
- * @private
- * @param {object} e XHR event
- */
-MediaUploader.prototype.onUploadError_ = function (e) {
- this.onError(e.target.response) // TODO - Retries for initial upload
-}
-
-/**
- * Construct a query string from a hash/object
- *
- * @private
- * @param {object} [params] Key/value pairs for query string
- * @return {string} query string
- */
-MediaUploader.prototype.buildQuery_ = function (params) {
- params = params || {}
- return Object.keys(params).map(function (key) {
- return encodeURIComponent(key) + '=' + encodeURIComponent(params[key])
- }).join('&')
-}
-
-/**
- * Build the drive upload URL
- *
- * @private
- * @param {string} [id] File ID if replacing
- * @param {object} [params] Query parameters
- * @return {string} URL
- */
-MediaUploader.prototype.buildUrl_ = function (id, params, baseUrl) {
- var url = baseUrl || 'https://www.googleapis.com/upload/drive/v2/files/'
- if (id) {
- url += id
- }
- var query = this.buildQuery_(params)
- if (query) {
- url += '?' + query
- }
- return url
-}
-
-window.MediaUploader = MediaUploader
diff --git a/public/js/index.js b/public/js/index.js
index d76a37fe..c6a4f770 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -30,8 +30,6 @@ import {
import {
debug,
DROPBOX_APP_KEY,
- GOOGLE_API_KEY,
- GOOGLE_CLIENT_ID,
noteid,
noteurl,
urlpath,
@@ -908,29 +906,6 @@ if (DROPBOX_APP_KEY) {
ui.toolbar.export.dropbox.hide()
}
-// check if google api key and client id are set and load scripts
-if (GOOGLE_API_KEY && GOOGLE_CLIENT_ID) {
- $('<script>')
- .attr('type', 'text/javascript')
- .attr('src', 'https://www.google.com/jsapi?callback=onGoogleAPILoaded')
- .prop('async', true)
- .prop('defer', true)
- .appendTo('body')
-} else {
- ui.toolbar.import.googleDrive.hide()
- ui.toolbar.export.googleDrive.hide()
-}
-
-function onGoogleAPILoaded () {
- $('<script>')
- .attr('type', 'text/javascript')
- .attr('src', 'https://apis.google.com/js/client:plusone.js?onload=onGoogleClientLoaded')
- .prop('async', true)
- .prop('defer', true)
- .appendTo('body')
-}
-window.onGoogleAPILoaded = onGoogleAPILoaded
-
// button actions
// share
ui.toolbar.publish.attr('href', noteurl + '/publish')
@@ -979,53 +954,6 @@ ui.toolbar.export.dropbox.click(function () {
}
Dropbox.save(options)
})
-function uploadToGoogleDrive (accessToken) {
- ui.spinner.show()
- var filename = renderFilename(ui.area.markdown) + '.md'
- var markdown = editor.getValue()
- var blob = new Blob([markdown], {
- type: 'text/markdown;charset=utf-8'
- })
- blob.name = filename
- var uploader = new MediaUploader({
- file: blob,
- token: accessToken,
- onComplete: function (data) {
- data = JSON.parse(data)
- showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Complete!', data.alternateLink, 'Click here to view your file', true)
- ui.spinner.hide()
- },
- onError: function (data) {
- showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Error :(', '', data, false)
- ui.spinner.hide()
- }
- })
- uploader.upload()
-}
-function googleApiAuth (immediate, callback) {
- gapi.auth.authorize(
- {
- 'client_id': GOOGLE_CLIENT_ID,
- 'scope': 'https://www.googleapis.com/auth/drive.file',
- 'immediate': immediate
- }, callback || function () { })
-}
-function onGoogleClientLoaded () {
- googleApiAuth(true)
- buildImportFromGoogleDrive()
-}
-window.onGoogleClientLoaded = onGoogleClientLoaded
-// export to google drive
-ui.toolbar.export.googleDrive.click(function (e) {
- var token = gapi.auth.getToken()
- if (token) {
- uploadToGoogleDrive(token.access_token)
- } else {
- googleApiAuth(false, function (result) {
- uploadToGoogleDrive(result.access_token)
- })
- }
-})
// export to gist
ui.toolbar.export.gist.attr('href', noteurl + '/gist')
// export to snippet
@@ -1075,38 +1003,6 @@ ui.toolbar.import.dropbox.click(function () {
}
Dropbox.choose(options)
})
-// import from google drive
-function buildImportFromGoogleDrive () {
- /* eslint-disable no-unused-vars */
- let picker = new FilePicker({
- apiKey: GOOGLE_API_KEY,
- clientId: GOOGLE_CLIENT_ID,
- buttonEl: ui.toolbar.import.googleDrive,
- onSelect: function (file) {
- if (file.downloadUrl) {
- ui.spinner.show()
- var accessToken = gapi.auth.getToken().access_token
- $.ajax({
- type: 'GET',
- beforeSend: function (request) {
- request.setRequestHeader('Authorization', 'Bearer ' + accessToken)
- },
- url: file.downloadUrl,
- success: function (data) {
- if (file.fileExtension === 'html') { parseToEditor(data) } else { replaceAll(data) }
- },
- error: function (data) {
- showMessageModal('<i class="fa fa-cloud-download"></i> Import from Google Drive', 'Import failed :(', '', data, false)
- },
- complete: function () {
- ui.spinner.hide()
- }
- })
- }
- }
- })
- /* eslint-enable no-unused-vars */
-}
// import from gist
ui.toolbar.import.gist.click(function () {
// na
diff --git a/public/js/lib/common/constant.ejs b/public/js/lib/common/constant.ejs
index c0963635..a94b815e 100644
--- a/public/js/lib/common/constant.ejs
+++ b/public/js/lib/common/constant.ejs
@@ -5,6 +5,4 @@ window.version = '<%- version %>'
window.allowedUploadMimeTypes = <%- JSON.stringify(allowedUploadMimeTypes) %>
-window.GOOGLE_API_KEY = '<%- GOOGLE_API_KEY %>'
-window.GOOGLE_CLIENT_ID = '<%- GOOGLE_CLIENT_ID %>'
window.DROPBOX_APP_KEY = '<%- DROPBOX_APP_KEY %>'
diff --git a/public/js/lib/config/index.js b/public/js/lib/config/index.js
index 11e4389f..4758ffe7 100644
--- a/public/js/lib/config/index.js
+++ b/public/js/lib/config/index.js
@@ -1,5 +1,3 @@
-export const GOOGLE_API_KEY = window.GOOGLE_API_KEY || ''
-export const GOOGLE_CLIENT_ID = window.GOOGLE_CLIENT_ID || ''
export const DROPBOX_APP_KEY = window.DROPBOX_APP_KEY || ''
export const domain = window.domain || '' // domain name
diff --git a/public/js/lib/editor/ui-elements.js b/public/js/lib/editor/ui-elements.js
index 88a1e3ca..ca06d30c 100644
--- a/public/js/lib/editor/ui-elements.js
+++ b/public/js/lib/editor/ui-elements.js
@@ -22,13 +22,11 @@ export const getUIElements = () => ({
},
export: {
dropbox: $('.ui-save-dropbox'),
- googleDrive: $('.ui-save-google-drive'),
gist: $('.ui-save-gist'),
snippet: $('.ui-save-snippet')
},
import: {
dropbox: $('.ui-import-dropbox'),
- googleDrive: $('.ui-import-google-drive'),
gist: $('.ui-import-gist'),
snippet: $('.ui-import-snippet'),
clipboard: $('.ui-import-clipboard')
diff --git a/public/vendor/md-toc.js b/public/vendor/md-toc.js
index f93f7921..3457d465 100644
--- a/public/vendor/md-toc.js
+++ b/public/vendor/md-toc.js
@@ -54,6 +54,7 @@
var j = i + 1
this._elTitleElement = this.elTitleElements[i]
this._elTitleElementName = this._elTitleElement.tagName
+ this._elTitleElementTitle = this._elTitleElement.textContent.replace(/"/g, '&quot;')
this._elTitleElementText = (typeof this.process === 'function' ? this.process(this._elTitleElement) : this._elTitleElement.innerHTML).replace(/<(?:.|\n)*?>/gm, '')
var id = this._elTitleElement.getAttribute('id')
if (!id) {
@@ -63,7 +64,7 @@
id = '#' + id
}
- this.tocContent += '<li><a href="' + id + '">' + this._elTitleElementText + '</a>'
+ this.tocContent += '<li><a href="' + id + '" title="'+ this._elTitleElementTitle +'">' + this._elTitleElementText + '</a>'
if (j !== this._elTitleElementsLen) {
this._elNextTitleElementName = this.elTitleElements[j].tagName
diff --git a/public/views/hackmd/header.ejs b/public/views/hackmd/header.ejs
index e179f171..21b632ce 100644
--- a/public/views/hackmd/header.ejs
+++ b/public/views/hackmd/header.ejs
@@ -32,13 +32,11 @@
</li>
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
</li>
- <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof google !== 'undefined' && google) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
+ <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
<li class="divider"></li>
<li class="dropdown-header"><%= __('Export') %></li>
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
</li>
- <li role="presentation"><a role="menuitem" class="ui-save-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-upload fa-fw"></i> Google Drive</a>
- </li>
<% if(typeof github !== 'undefined' && github) { %>
<li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank"><i class="fa fa-github fa-fw"></i> Gist</a>
</li>
@@ -52,8 +50,6 @@
<li class="dropdown-header"><%= __('Import') %></li>
<li role="presentation"><a role="menuitem" class="ui-import-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
</li>
- <li role="presentation"><a role="menuitem" class="ui-import-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-download fa-fw"></i> Google Drive</a>
- </li>
<li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a>
</li>
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>
@@ -138,13 +134,11 @@
</li>
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
</li>
- <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof google !== 'undefined' && google) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
+ <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
<li class="divider"></li>
<li class="dropdown-header"><%= __('Export') %></li>
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
</li>
- <li role="presentation"><a role="menuitem" class="ui-save-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-upload fa-fw"></i> Google Drive</a>
- </li>
<% if(typeof github !== 'undefined' && github) { %>
<li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank"><i class="fa fa-github fa-fw"></i> Gist</a>
</li>
@@ -158,8 +152,6 @@
<li class="dropdown-header"><%= __('Import') %></li>
<li role="presentation"><a role="menuitem" class="ui-import-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
</li>
- <li role="presentation"><a role="menuitem" class="ui-import-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-download fa-fw"></i> Google Drive</a>
- </li>
<li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a>
</li>
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>
diff --git a/public/views/shared/disqus.ejs b/public/views/shared/disqus.ejs
index cceaa85c..840d1e38 100644
--- a/public/views/shared/disqus.ejs
+++ b/public/views/shared/disqus.ejs
@@ -1,14 +1,13 @@
<div id="disqus_thread"></div>
-<script>
+<script nonce="<%= cspNonce %>">
var disqus_config = function () {
this.page.identifier = window.location.pathname.split('/').slice(-1)[0];
};
(function() {
var d = document, s = d.createElement('script');
- s.src = '//<%= disqus %>.disqus.com/embed.js';
+ s.src = 'https://<%= disqus %>.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
- \ No newline at end of file
diff --git a/public/views/shared/ga.ejs b/public/views/shared/ga.ejs
index 66d4acd9..27abb742 100644
--- a/public/views/shared/ga.ejs
+++ b/public/views/shared/ga.ejs
@@ -1,5 +1,5 @@
<% if(typeof GA !== 'undefined' && GA) { %>
-<script>
+<script nonce="<%= cspNonce %>">
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function () {
@@ -10,9 +10,9 @@
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
-})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
+})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga('create', '<%= GA %>', 'auto');
ga('send', 'pageview');
</script>
-<% } %> \ No newline at end of file
+<% } %>
diff --git a/public/views/shared/help-modal.ejs b/public/views/shared/help-modal.ejs
index f5dc55c2..6bcf637e 100644
--- a/public/views/shared/help-modal.ejs
+++ b/public/views/shared/help-modal.ejs
@@ -17,7 +17,9 @@
<div class="panel-body">
<a href="https://github.com/hackmdio/hackmd/issues" target="_blank"><i class="fa fa-tag fa-fw"></i> <%= __('Report an issue') %></a>
<br>
- <a href="https://gitter.im/hackmdio/hackmd" target="_blank"><i class="fa fa-comments fa-fw"></i> <%= __('Meet us on Gitter') %></a>
+ <a href="https://riot.im/app/#/room/#hackmd:matrix.org" target="_blank"><i class="fa fa-hashtag fa-fw"></i> <%= __('Meet us on %s', 'Matrix') %></a>
+ <br>
+ <a href="https://gitter.im/hackmdio/hackmd" target="_blank"><i class="fa fa-comments fa-fw"></i> <%= __('Meet us on %s', 'Gitter') %></a>
</div>
</div>
<div class="panel panel-default">