Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Vue components, mount options, global options, and v3 support #1409

Merged
merged 45 commits into from
Nov 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
35ad5c5
Add vueOptions and vueGlobalOptions
jhildenbiddle Oct 10, 2020
cd8f2d3
Fix vueGlobalOptions.data checks
jhildenbiddle Oct 10, 2020
ecd053b
Support functions in docsify configuration
jhildenbiddle Oct 15, 2020
5fa79eb
Fix Vue content detection and global options
jhildenbiddle Oct 15, 2020
a2386e5
Add vueComponents support
jhildenbiddle Oct 16, 2020
bc2e091
Ignore `el` property in Vue configs for Vue v2
jhildenbiddle Oct 16, 2020
1774ab8
Initial commit
jhildenbiddle Oct 17, 2020
6d152e2
Refactor regular expression usage
jhildenbiddle Oct 17, 2020
758538c
Add `<output>`. Refactor `<pre>` and `<code>`.
jhildenbiddle Oct 17, 2020
0193659
Update docs with new Vue features and usage
jhildenbiddle Oct 17, 2020
8a56f72
Fix directive detection
jhildenbiddle Oct 18, 2020
5a2dde1
Update docs
jhildenbiddle Oct 20, 2020
1a52e97
Update docs
jhildenbiddle Oct 21, 2020
86dae61
Rename `vueOptions` to `vueMountOptions`
jhildenbiddle Oct 21, 2020
2946c06
Update test
jhildenbiddle Oct 21, 2020
871e48f
Update default waitForSelector value
jhildenbiddle Oct 21, 2020
131aae5
Fix waitForText() timeout and reject messages
jhildenbiddle Oct 23, 2020
af7e713
Change default startPath for tests & manual start
jhildenbiddle Oct 23, 2020
25084f8
Update jest/docsify side-effect cleanup
jhildenbiddle Oct 23, 2020
6a5e84b
Update directive detection (data vs static)
jhildenbiddle Oct 23, 2020
febe7f8
Remove global event listeners beforeEach
jhildenbiddle Oct 25, 2020
b6d5407
Fix basePath
jhildenbiddle Oct 25, 2020
8362ed0
Remove unnecessary globals
jhildenbiddle Oct 26, 2020
f0a8d69
Fix basePath update when config is function
jhildenbiddle Oct 26, 2020
5ec2256
Fix basePath & update route URLs
jhildenbiddle Oct 26, 2020
a9be6f2
Merge branch 'develop' into feat/vue-options
jhildenbiddle Oct 26, 2020
3af1cfa
Add selector option to _logHTML
jhildenbiddle Oct 26, 2020
b33d750
Remove SRC_PATH global variable
jhildenbiddle Oct 26, 2020
cafdd62
Remove —runInBand flag from test script
jhildenbiddle Oct 26, 2020
d9e491a
Fix covepage test
jhildenbiddle Oct 26, 2020
a5a431f
Fix base element reset
jhildenbiddle Nov 7, 2020
4e5dae7
Squashed commit of the following:
jhildenbiddle Nov 11, 2020
eae3ad8
Remove markdownlint config
jhildenbiddle Nov 11, 2020
421ef1d
Update docs
jhildenbiddle Nov 11, 2020
52cce02
Delete unnecessary file
jhildenbiddle Nov 11, 2020
727ee8f
Rename `vueMountOptions` to `vueMounts`
jhildenbiddle Nov 11, 2020
69efae3
Fix typos
jhildenbiddle Nov 11, 2020
51451a4
Remove BLANK_URL global
jhildenbiddle Nov 11, 2020
a10fcbd
Update Vue-related descriptions
jhildenbiddle Nov 12, 2020
9a1935a
Add support for Vue shorthand directive syntax
jhildenbiddle Nov 15, 2020
43a73bb
Merge branch 'develop' into feat/vue-options
sy-records Nov 16, 2020
7cf1e7c
Update Vue-related descriptions and links
jhildenbiddle Nov 19, 2020
7878c60
Merge branch 'develop' into feat/vue-options
jhildenbiddle Nov 19, 2020
90b67e2
Merge branch 'develop' into feat/vue-options
sy-records Nov 23, 2020
ffa7c90
Fix Lint
sy-records Nov 23, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions cypress.json

This file was deleted.

121 changes: 111 additions & 10 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,10 @@ window.$docsify = {
// Custom file name
coverpage: 'cover.md',

// mutiple covers
// multiple covers
coverpage: ['/', '/zh-cn/'],

// mutiple covers and custom file name
// multiple covers and custom file name
coverpage: {
'/': 'cover.md',
'/zh-cn/': 'cover.md',
Expand Down Expand Up @@ -410,7 +410,7 @@ window.$docsify = {
};
```

?> If this options is `false` but you dont want to emojify some specific colons , [Refer this](https://github.com/docsifyjs/docsify/issues/742#issuecomment-586313143)
?> If this options is `false` but you don't want to emojify some specific colons , [Refer this](https://github.com/docsifyjs/docsify/issues/742#issuecomment-586313143)

## mergeNavbar

Expand Down Expand Up @@ -494,15 +494,15 @@ window.$docsify = {
```

## crossOriginLinks
- type: `Array`

When `routerMode: 'history'`, you may face the cross-origin issues, See [#1379](https://github.com/docsifyjs/docsify/issues/1379).
In Markdown content, there is a simple way to solve it, see extends Markdown syntax `Cross-Origin link` in [helpers](helpers.md).
- type: `Array`

When `routerMode: 'history'`, you may face the cross-origin issues, See [#1379](https://github.com/docsifyjs/docsify/issues/1379).
In Markdown content, there is a simple way to solve it, see extends Markdown syntax `Cross-Origin link` in [helpers](helpers.md).

```js
window.$docsify = {
crossOriginLinks:[
"https://example.com/cross-origin-link",
],
crossOriginLinks: ['https://example.com/cross-origin-link'],
};
```

Expand Down Expand Up @@ -604,7 +604,7 @@ window.$docsify = {
};
```

Load the right 404 page according to the localisation:
Load the right 404 page according to the localization:

```js
window.$docsify = {
Expand All @@ -629,3 +629,104 @@ window.$docsify = {
topMargin: 90, // default: 0
};
```

## vueComponents

- type: `Object`

Creates and registers global [Vue components](https://vuejs.org/v2/guide/components.html). Components are specified using the component name as the key with an object containing Vue options as the value. Component `data` is unique for each instance and will not persist as users navigate the site.

```js
window.$docsify = {
vueComponents: {
'button-counter': {
template: `
<button @click="count += 1">
You clicked me {{ count }} times
</button>
`,
data() {
return {
count: 0,
};
},
},
},
};
```
Copy link
Member

@trusktr trusktr Nov 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we want to use vueMountOptions with a selector, when we could specify components using vueComponents?

Trying to get a more clear picture just from the docs (will read source next).

Copy link
Member Author

@jhildenbiddle jhildenbiddle Nov 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trusktr --

Fair question considering there is quite a bit of overlap between mounting DOM elements directly (vueMounts) and components (vueComponents). I'm not sure if you're asking why "we" as users or "we" as maintainers of docsify, so I'll try to touch on both.

The short answer is that all of the reasons why a user might opt to mount a DOM element directly instead of creating a component apply here. Similarly, all of the reasons why the Vue team provide the option to mount DOM elements directly instead of forcing devs to create Vue components for every scenario also apply here.

Here are a few items worth considering:

  1. Vue supports mounting both DOM elements and components. So should we.

  2. Mounting DOM elements is the first concept found in the Vue docs and introductory Vue tutorials. It is the fastest and simplest way to be productive with a library like Vue and requires the least amount of expertise. Allowing code from these docs and tutorials to be easily ported to docsify is an important consideration for our users--especially junior and non-developer users.

  3. Components are typically presented as reusable building blocks that combine to make larger applications. Obviously this doesn't have to be the case, but it's how they are often conceptualized. Users shouldn't have to think in these terms or learn about Vue components if they only want to do simple things like render data on a docsify page.

  4. There are technical differences to consider when choosing to mount a DOM element or a component. For example, vueMountOptions vueMounts allows Vue templates to be defined inline (in .md pages) and include markdown syntax:

    <div id="my-content">
    ## {{ title }}
    Here is some Vue content: {{ message }}
    </div>
    vueMounts: {
      '#my-content': {
        data() {
          return {
            title: 'My Title',
            message: 'Hello, World!'
          };
        }
      }
    }

    Vue components more commonly define templates via the template property:

    vueComponents: {
      'my-component': {
        template: `
          <h2>{{ title }}</h2>
          <p>Here is some Vue content: {{ message }}</p>
        `,
        data() {
          return {
            count: 0,
          };
        }
      }
    }

    The drawback to defining templates this way is that template markup cannot contain markdown syntax, will not be processed by docsify plugins, will not have syntax highlighting or linting while authoring, will not render default content outside of docsify (e.g., GitHub), etc. You can work around some of these things, but doing so requires a level of expertise that not all docsify users will have.

    FWIW, Vue components can do inline templates, but doing so requires adding an inline-template attribute to the component tag (which is not intuitive) and repeating inline template markup kinda defeats the purpose of simple, reusable components.

    <my-component inline-template>
      <h2>{{ title }}</h2>
      <p>Here is some Vue content: {{ message }}</p>
    </my-component>

I'm sure there are other items worth considering that I haven't thought of, but hopefully the above items are helpful.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that explanation! I know about the starting the root of a Vue tree and components used in the Vue tree, but these are two features additional to Vue (for Docsify).

Knowing Vue, I know I can put new Vue in a script that I can load with <script src="..."> on every page.

I suppose what I'm really asking is the following.

Here are two ways to basically do the same thing with the new system.

Method 1:

<div id="my-content">

## {{ title }}
Here is some Vue content: {{ message }}

</div>
vueMounts: {
  '#my-content': {
    data: () => ({
      title: 'My Title',
      message: 'Hello, World!'
    })
  }
}

Method 2:

<my-content inline-template>

## {{ title }}
Here is some Vue content: {{ message }}

</my-content>
vueComponents: {
  'my-content': {
    data: () => ({
      title: 'My Title',
      message: 'Hello, World!'
    })
  }
}

Both have about the same ease-of-use (learning to use inline-template is as easy as writing an id or class name). The question is, why have both?

Is the difference only the inline-template vs no inline-template thing? If so, is it worth having two options just for that small difference?


```markdown
<button-counter></button-counter>
```

<output data-lang="output">
<button-counter></button-counter>
</output>

## vueGlobalOptions

- type: `Object`

Specifies [Vue options](https://vuejs.org/v2/api/#Options-Data) for use with Vue content not explicitly mounted with [vueMounts](#mounting-dom-elements), [vueComponents](#components), or a [markdown script](#markdown-script). Changes to global `data` will persist and be reflected anywhere global references are used.

```js
window.$docsify = {
vueGlobalOptions: {
data() {
return {
count: 0,
};
},
},
};
```

```markdown
<p>
<button @click="count -= 1">-</button>
{{ count }}
<button @click="count += 1">+</button>
</p>
```

<output data-lang="output">
<p>
<button @click="count -= 1">-</button>
{{ count }}
<button @click="count += 1">+</button>
</p>
</output>

## vueMounts

- type: `Object`

Specifies DOM elements to mount as [Vue instances](https://vuejs.org/v2/guide/instance.html) and their associated options. Mount elements are specified using a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) as the key with an object containing Vue options as their value. Docsify will mount the first matching element in the main content area each time a new page is loaded. Mount element `data` is unique for each instance and will not persist as users navigate the site.

```js
window.$docsify = {
vueMounts: {
'#counter': {
data() {
return {
count: 0,
};
},
},
},
};
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that there's explanation missing here as to what exactly this does.

If the vueMountOptions contains 5 selectors, does that mean Vue instantiates 5 components and mounts to those five areas on the page?

Copy link
Member Author

@jhildenbiddle jhildenbiddle Nov 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trusktr Agreed. Good catch. This question is answered on the vue.md page, but I neglected to add it to the description here as well.

Docsify will mount the first matching element in the main content area (#main, .markdown-section) each time a new page is loaded.

For example:

window.$docsify = {
  vueMountOptions: {
    // The CSS selector "p" could match multiple elements.
    // However, only the first <p> element will be mounted.
    // Think `querySelector()`, not `querySelectorAll()`
    'p': {  
      data() {
        return {
          count: 0,
        };
      },
    },
  },
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have it apply to all matches? That would be convenient I imagine (one definition, but multiple instances of it across the page). Thinking querySelectorAll instead of querySelector.

Also people may wonder if the following examples will work:

Exampe 1

Does it render both?

<p class="one">{count}</p>
<p class="two">{count}</p>
window.$docsify = {
  vueMounts: {
    'p.one': {  
      data: () => ({ count: 1 },
    },
    'p.two': {  
      data: () => ({ count: 2 },
    },
  },
};

Example 2

What happens?

<p class="one">
  {count}
  <p class="two">
    {count}
  </p>
</p>
window.$docsify = {
  vueMounts: {
    'p.one': {  
      data: () => ({ count: 1 },
    },
    'p.two': {  
      data: () => ({ count: 2 },
    },
  },
};

Example 3

What happens?

<component-one inline-template>
  {count}
  <component-two inline-template>
    {count}
  </component-two>
</component-one>
window.$docsify = {
  vueComponents: {
    'component-one': {
      data: () => ({ count: 1 },
    },
    'component-two': {  
      data: () => ({ count: 2 },
    },
  },
};

Example 4

User opens feature request saying "can we render all of them?"

<p>{count}</p>
<p>{count}</p>
<p>{count}</p>
<p>{count}</p>
<p>{count}</p>
let pCount = 0

window.$docsify = {
  vueMounts: {
    'p': {  
      data: () => ({ count: ++pCount  },
    }
  },
};

Exampe 5

How do I make this work?

<p class="one">{count}{foo}</p>
<p class="two">{count}{foo}</p>
window.$docsify = {
  vueMounts: {
    'p.one': {
      data: () => ({ count: 1 },
    },
    'p.two': {
      data: () => ({ count: 2 },
    },
    'p': {
      data: () => ({ foo: 'bar' },
    },
  },
};


```markdown
<div id="counter">
<button @click="count -= 1">-</button>
{{ count }}
<button @click="count += 1">+</button>
</div>
```

<output id="counter">
<button @click="count -= 1">-</button>
{{ count }}
<button @click="count += 1">+</button>
</output>
57 changes: 57 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,62 @@
'/': 'Search',
},
},
vueComponents: {
'button-counter': {
template: `
<button @click="count += 1">
You clicked me {{ count }} times
</button>
`,
data() {
return {
count: 0,
};
},
},
},
vueGlobalOptions: {
trusktr marked this conversation as resolved.
Show resolved Hide resolved
data() {
return {
count: 0,
message: 'Hello, World!',
// Fake API response
images: [
{ title: 'Image 1', url: 'https://picsum.photos/150?random=1' },
{ title: 'Image 2', url: 'https://picsum.photos/150?random=2' },
{ title: 'Image 3', url: 'https://picsum.photos/150?random=3' },
],
};
},
computed: {
timeOfDay() {
const date = new Date();
const hours = date.getHours();

if (hours < 12) {
return 'morning';
} else if (hours < 18) {
return 'afternoon';
} else {
return 'evening';
}
},
},
methods: {
hello: function() {
alert(this.message);
},
},
},
vueMounts: {
'#counter': {
data() {
return {
count: 0,
};
},
},
},
plugins: [
function(hook, vm) {
hook.beforeEach(function(html) {
Expand Down Expand Up @@ -163,5 +219,6 @@
})();
</script>
<script src="//cdn.jsdelivr.net/npm/vue@2/dist/vue.min.js"></script>
<!-- <script src="//cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script> -->
</body>
</html>
Loading